From f7ec554a88e949eb7dba48aa238369732d977e70 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 31 Mar 2025 17:46:33 -0700 Subject: [PATCH 01/20] WIP package resource property definition --- .../AppInstallerCLICore.vcxproj | 2 + .../Commands/DscCommand.cpp | 2 + .../Commands/DscComposableObject.h | 33 ++- .../Commands/DscPackageResource.cpp | 236 ++++++++++++++++++ .../Commands/DscPackageResource.h | 25 ++ .../Commands/DscTestFileResource.cpp | 14 +- .../Commands/DscTestJsonResource.cpp | 14 +- src/AppInstallerCLICore/Resources.h | 2 + .../Shared/Strings/en-us/winget.resw | 6 + 9 files changed, 312 insertions(+), 22 deletions(-) create mode 100644 src/AppInstallerCLICore/Commands/DscPackageResource.cpp create mode 100644 src/AppInstallerCLICore/Commands/DscPackageResource.h diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 3973b16424..d327883e6b 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -305,6 +305,7 @@ + @@ -391,6 +392,7 @@ + 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/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 16fa2dc344..5ae200adc4 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include using namespace std::string_view_literals; @@ -33,7 +35,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, + std::string_view type, + std::string_view description, + const std::vector& enumValues, + const std::optional& defaultValue); } template @@ -135,7 +144,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::SchemaTypeName(), Property::Description(), Property::EnumValues(), Property::Default())); return result; } }; @@ -190,23 +199,31 @@ 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 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_, {}, {}) - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(StandardExistProperty, bool, Exist, "_exist", DscComposablePropertyFlag::None, "Indicates whether an instance should/does exist.") +#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_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_) + + 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); } }; diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp new file mode 100644 index 0000000000..58baa99c6e --- /dev/null +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscPackageResource.h" +#include "DscComposableObject.h" +#include "Resources.h" + +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI +{ + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(IdProperty, std::string, Identifier, "id", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The identifier of the package."); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SourceProperty, std::string, Source, "source", DscComposablePropertyFlag::CopyToOutput, "The source of the package."); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(VersionProperty, std::string, Version, "version", "The version of the package."); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(MatchOptionProperty, std::string, MatchOption, "matchOption", "The method for matching the identifier with a package.", ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); + + using PackageResourceObject = DscComposableObject; + + struct PackageFunctionData + { + PackageFunctionData(const std::optional& json) : Input(json), Output(Input.CopyForOutput()) + { + } + + PackageResourceObject Input; + PackageResourceObject Output; + + // Fills the Output object with the current state + void Get() + { + if (std::filesystem::exists(Path) && std::filesystem::is_regular_file(Path)) + { + Output.Exist(true); + + std::ifstream stream{ Path, std::ios::binary }; + Output.Content(Utility::ReadEntireStream(stream)); + } + else + { + Output.Exist(false); + } + } + + // 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()) + { + return ContentMatches(); + } + else + { + return false; + } + } + else + { + 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 (!ContentMatches()) + { + result.append(std::string{ ContentProperty::Name() }); + } + } + + return result; + } + + private: + bool ContentMatches() + { + bool hasInput = Input.Content().has_value() && !Input.Content().value().empty(); + bool hasOutput = Output.Content().has_value() && !Output.Content().value().empty(); + + return + (hasInput && hasOutput && Input.Content().value() == Output.Content().value()) || + (!hasInput && !hasOutput); + } + }; + } + + DscPackageResource::DscPackageResource(std::string_view parent) : + DscCommandBase(parent, "package", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::WhatIf | 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)) + { + anon::TestFileFunctionData data{ json }; + + data.Get(); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscPackageResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + anon::TestFileFunctionData data{ json }; + + data.Get(); + + if (!data.Test()) + { + bool exists = std::filesystem::exists(data.Path); + if (exists) + { + // Don't delete a directory or other special files in this test resource + THROW_WIN32_IF(ERROR_DIRECTORY_NOT_SUPPORTED, !std::filesystem::is_regular_file(data.Path)); + } + + if (data.Input.ShouldExist()) + { + std::filesystem::create_directories(data.Path.parent_path()); + + std::ofstream stream{ data.Path, std::ios::binary | std::ios::trunc }; + if (data.Input.Content()) + { + stream.write(data.Input.Content().value().c_str(), data.Input.Content().value().length()); + } + } + else if (exists) + { + std::filesystem::remove(data.Path); + } + } + + // Capture the diff before updating the output + auto diff = data.DiffJson(); + + data.Output.Exist(data.Input.ShouldExist()); + if (data.Output.Exist().value()) + { + data.Output.Content(data.Input.Content().value_or("")); + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, diff); + } + } + + void DscPackageResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + anon::TestFileFunctionData data{ json }; + + data.Get(); + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson()); + } + } + + void DscPackageResource::ResourceFunctionExport(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + anon::TestFileFunctionData data{ json }; + + if (std::filesystem::exists(data.Path)) + { + if (std::filesystem::is_regular_file(data.Path)) + { + data.Get(); + WriteJsonOutputLine(context, data.Output.ToJson()); + } + else if (std::filesystem::is_directory(data.Path)) + { + for (const auto& file : std::filesystem::directory_iterator{ data.Path }) + { + if (std::filesystem::is_regular_file(file)) + { + anon::TestFileObject output; + output.Path(file.path().u8string()); + + std::ifstream stream{ file.path(), std::ios::binary}; + output.Content(Utility::ReadEntireStream(stream)); + + WriteJsonOutputLine(context, output.ToJson()); + } + } + } + } + } + } + + void DscPackageResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, anon::TestFileObject::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..8bbb4274d6 100644 --- a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp @@ -8,7 +8,7 @@ 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."); @@ -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..12186deabb 100644 --- a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp @@ -9,7 +9,7 @@ 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."); @@ -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/Resources.h b/src/AppInstallerCLICore/Resources.h index 43d42b9403..6bc02de25c 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -212,6 +212,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); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 5ec1036985..64c094f6d3 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3247,4 +3247,10 @@ 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. + \ No newline at end of file From a8efbae6cd5fec646efb5050dd99448100b84b2c Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 1 Apr 2025 17:03:18 -0700 Subject: [PATCH 02/20] Package resource property parity --- .../Commands/DscComposableObject.cpp | 56 +++++++++- .../Commands/DscComposableObject.h | 24 ++-- .../Commands/DscPackageResource.cpp | 103 ++++-------------- .../Commands/DscPackageResource.h | 1 + 4 files changed, 91 insertions(+), 93 deletions(-) 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 5ae200adc4..45e714a259 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -39,7 +39,7 @@ namespace AppInstaller::CLI Json::Value& object, std::string_view name, DscComposablePropertyFlag flags, - std::string_view type, + Json::ValueType type, std::string_view description, const std::vector& enumValues, const std::optional& defaultValue); @@ -59,9 +59,9 @@ namespace AppInstaller::CLI return value.asBool(); } - static std::string_view SchemaTypeName() + static Json::ValueType SchemaType() { - return "boolean"sv; + return Json::ValueType::booleanValue; } }; @@ -73,9 +73,9 @@ namespace AppInstaller::CLI return value.asString(); } - static std::string_view SchemaTypeName() + static Json::ValueType SchemaType() { - return "string"sv; + return Json::ValueType::stringValue; } }; @@ -87,10 +87,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; } }; @@ -144,7 +143,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(), Property::EnumValues(), Property::Default())); + (FoldHelper{}, ..., details::AddPropertySchema(result, Property::Name(), Property::Flags, GetJsonTypeValue::SchemaType(), Property::Description(), Property::EnumValues(), Property::Default())); return result; } }; @@ -204,8 +203,8 @@ namespace AppInstaller::CLI { \ static std::string_view Name() { return _json_name_; } \ static std::string_view Description() { return _description_; } \ - static std::vector EnumValues() { return std::vector _enum_vals_; } \ - static std::optional Default() { return _default_; } \ + 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); } \ @@ -220,6 +219,9 @@ namespace AppInstaller::CLI #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_) diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 58baa99c6e..4a1cf00972 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -15,8 +15,10 @@ namespace AppInstaller::CLI WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SourceProperty, std::string, Source, "source", DscComposablePropertyFlag::CopyToOutput, "The source of the package."); WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(VersionProperty, std::string, Version, "version", "The version of the package."); WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(MatchOptionProperty, std::string, MatchOption, "matchOption", "The method for matching the identifier with a package.", ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(UseLatestProperty, bool, UseLatest, "useLatest", "Indicate that the latest available version of the package should be installed.", "false"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(InstallModeProperty, std::string, InstallMode, "installMode", "The install mode to use if needed.", ({ "default", "silent", "interactive" }), "silent"); - using PackageResourceObject = DscComposableObject; + using PackageResourceObject = DscComposableObject; struct PackageFunctionData { @@ -30,17 +32,7 @@ namespace AppInstaller::CLI // Fills the Output object with the current state void Get() { - if (std::filesystem::exists(Path) && std::filesystem::is_regular_file(Path)) - { - Output.Exist(true); - - std::ifstream stream{ Path, std::ios::binary }; - Output.Content(Utility::ReadEntireStream(stream)); - } - else - { - Output.Exist(false); - } + THROW_HR(E_NOTIMPL); } // Determines if the current Output values match the Input values state. @@ -53,7 +45,7 @@ namespace AppInstaller::CLI { if (Output.Exist().value()) { - return ContentMatches(); + THROW_HR(E_NOTIMPL); } else { @@ -79,25 +71,11 @@ namespace AppInstaller::CLI } else { - if (!ContentMatches()) - { - result.append(std::string{ ContentProperty::Name() }); - } + THROW_HR(E_NOTIMPL); } return result; } - - private: - bool ContentMatches() - { - bool hasInput = Input.Content().has_value() && !Input.Content().value().empty(); - bool hasOutput = Output.Content().has_value() && !Output.Content().value().empty(); - - return - (hasInput && hasOutput && Input.Content().value() == Output.Content().value()) || - (!hasInput && !hasOutput); - } }; } @@ -127,7 +105,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + PackageFunctionData data{ json }; data.Get(); @@ -139,33 +117,13 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + PackageFunctionData data{ json }; data.Get(); if (!data.Test()) { - bool exists = std::filesystem::exists(data.Path); - if (exists) - { - // Don't delete a directory or other special files in this test resource - THROW_WIN32_IF(ERROR_DIRECTORY_NOT_SUPPORTED, !std::filesystem::is_regular_file(data.Path)); - } - - if (data.Input.ShouldExist()) - { - std::filesystem::create_directories(data.Path.parent_path()); - - std::ofstream stream{ data.Path, std::ios::binary | std::ios::trunc }; - if (data.Input.Content()) - { - stream.write(data.Input.Content().value().c_str(), data.Input.Content().value().length()); - } - } - else if (exists) - { - std::filesystem::remove(data.Path); - } + THROW_HR(E_NOTIMPL); } // Capture the diff before updating the output @@ -174,7 +132,7 @@ namespace AppInstaller::CLI data.Output.Exist(data.Input.ShouldExist()); if (data.Output.Exist().value()) { - data.Output.Content(data.Input.Content().value_or("")); + THROW_HR(E_NOTIMPL); } WriteJsonOutputLine(context, data.Output.ToJson()); @@ -182,11 +140,21 @@ namespace AppInstaller::CLI } } + void DscPackageResource::ResourceFunctionWhatIf(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + PackageFunctionData data{ json }; + + THROW_HR(E_NOTIMPL); + } + } + void DscPackageResource::ResourceFunctionTest(Execution::Context& context) const { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + PackageFunctionData data{ json }; data.Get(); data.Output.InDesiredState(data.Test()); @@ -200,37 +168,14 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::TestFileFunctionData data{ json }; + PackageFunctionData data{ json }; - if (std::filesystem::exists(data.Path)) - { - if (std::filesystem::is_regular_file(data.Path)) - { - data.Get(); - WriteJsonOutputLine(context, data.Output.ToJson()); - } - else if (std::filesystem::is_directory(data.Path)) - { - for (const auto& file : std::filesystem::directory_iterator{ data.Path }) - { - if (std::filesystem::is_regular_file(file)) - { - anon::TestFileObject output; - output.Path(file.path().u8string()); - - std::ifstream stream{ file.path(), std::ios::binary}; - output.Content(Utility::ReadEntireStream(stream)); - - WriteJsonOutputLine(context, output.ToJson()); - } - } - } - } + THROW_HR(E_NOTIMPL); } } void DscPackageResource::ResourceFunctionSchema(Execution::Context& context) const { - WriteJsonOutputLine(context, anon::TestFileObject::Schema(ResourceType())); + WriteJsonOutputLine(context, PackageResourceObject::Schema(ResourceType())); } } diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.h b/src/AppInstallerCLICore/Commands/DscPackageResource.h index 3bc0787096..919cacd37e 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.h +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.h @@ -18,6 +18,7 @@ namespace AppInstaller::CLI void ResourceFunctionGet(Execution::Context& context) const override; void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionWhatIf(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; From 6391e1b4323f67e68eb61cb38ca3b88a2e28cc61 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 2 Apr 2025 12:33:42 -0700 Subject: [PATCH 03/20] Localization for property descriptions --- .../AppInstallerCLICore.vcxproj.filters | 6 ++++ .../Commands/DscComposableObject.h | 9 +++-- .../Commands/DscPackageResource.cpp | 36 ++++++++++++++----- .../Commands/DscTestFileResource.cpp | 4 +-- .../Commands/DscTestJsonResource.cpp | 4 +-- src/AppInstallerCLICore/Resources.h | 8 +++++ .../Shared/Strings/en-us/winget.resw | 24 +++++++++++++ 7 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index a2407b19ff..9b4ed71f2a 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -284,6 +284,9 @@ Commands\Configuration + + Commands\Configuration + @@ -535,6 +538,9 @@ Commands\Configuration + + Commands\Configuration + diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 45e714a259..6f1d961465 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -4,12 +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 { @@ -202,7 +205,7 @@ namespace AppInstaller::CLI 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; } \ @@ -225,9 +228,9 @@ namespace AppInstaller::CLI #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_) - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(StandardExistProperty, bool, Exist, "_exist", DscComposablePropertyFlag::None, "Indicates whether an instance should/does exist.", {}, {}) + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(StandardExistProperty, bool, Exist, "_exist", DscComposablePropertyFlag::None, Resource::String::DscResourcePropertyDescriptionExist, {}, {}) bool ShouldExist() { 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 index 4a1cf00972..8ce581cc36 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -4,6 +4,7 @@ #include "DscPackageResource.h" #include "DscComposableObject.h" #include "Resources.h" +#include "Workflows/WorkflowBase.h" using namespace AppInstaller::Utility::literals; @@ -11,28 +12,47 @@ namespace AppInstaller::CLI { namespace { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(IdProperty, std::string, Identifier, "id", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The identifier of the package."); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SourceProperty, std::string, Source, "source", DscComposablePropertyFlag::CopyToOutput, "The source of the package."); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(VersionProperty, std::string, Version, "version", "The version of the package."); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(MatchOptionProperty, std::string, MatchOption, "matchOption", "The method for matching the identifier with a package.", ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(UseLatestProperty, bool, UseLatest, "useLatest", "Indicate that the latest available version of the package should be installed.", "false"); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(InstallModeProperty, std::string, InstallMode, "installMode", "The install mode to use if needed.", ({ "default", "silent", "interactive" }), "silent"); + 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"); using PackageResourceObject = DscComposableObject; struct PackageFunctionData { - PackageFunctionData(const std::optional& json) : Input(json), Output(Input.CopyForOutput()) + PackageFunctionData(Execution::Context& context, const std::optional& json) : + Input(json), + Output(Input.CopyForOutput()), + ParentContext(context), + SubContext(context.CreateSubContext()) { + SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity); } PackageResourceObject Input; PackageResourceObject Output; + Execution::Context& ParentContext; + std::unique_ptr SubContext; // Fills the Output object with the current state void Get() { - THROW_HR(E_NOTIMPL); + if (Input.Source()) + { + SubContext->Args.AddArg(Execution::Args::Type::Source, Input.Source().value()); + } + + *SubContext << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(*SubContext)); + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + } } // Determines if the current Output values match the Input values state. diff --git a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp index 8bbb4274d6..63a59ea04f 100644 --- a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp @@ -10,8 +10,8 @@ namespace AppInstaller::CLI { 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; diff --git a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp index 12186deabb..595ffb704b 100644 --- a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp @@ -11,8 +11,8 @@ namespace AppInstaller::CLI { 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; diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 6bc02de25c..b09071f9ac 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -225,6 +225,14 @@ 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(DscResourcePropertyDescriptionPackageMatchOption); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode); WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed); WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess); WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 64c094f6d3..2875f9878c 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3253,4 +3253,28 @@ Please specify one of them using the --source option to proceed. 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. + \ No newline at end of file From 71c6e8faa5a44aa9fff4e12c58932267cf3ed495 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 2 Apr 2025 17:43:34 -0700 Subject: [PATCH 04/20] Get maybe implemented? --- .../Commands/DscComposableObject.h | 3 + .../Commands/DscPackageResource.cpp | 97 +++++++++++++++++-- src/AppInstallerCLICore/Resources.h | 2 + .../Shared/Strings/en-us/winget.resw | 6 ++ 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 6f1d961465..783e183483 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -228,6 +228,9 @@ namespace AppInstaller::CLI #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, Resource::String::DscResourcePropertyDescriptionExist, {}, {}) bool ShouldExist() { return m_value.value_or(true); } }; diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 8ce581cc36..bd0804dd00 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -7,6 +7,7 @@ #include "Workflows/WorkflowBase.h" using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Repository; namespace AppInstaller::CLI { @@ -15,11 +16,42 @@ namespace AppInstaller::CLI 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_ENUM_FLAGS(ScopeProperty, std::string, Scope, "scope", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageScope, ({ "user", "machine" }), {}); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM_FLAGS(MatchOptionProperty, std::string, MatchOption, "matchOption", DscComposablePropertyFlag::CopyToOutput, 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); - using PackageResourceObject = DscComposableObject; + 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 == "equalscaseinsensitive") + { + return MatchType::CaseInsensitive; + } + else if (lowerValue == "startswithcaseinsensitive") + { + return MatchType::StartsWith; + } + else if (lowerValue == "containscaseinsensitive") + { + return MatchType::Substring; + } + + THROW_HR(E_INVALIDARG); + } struct PackageFunctionData { @@ -30,6 +62,12 @@ namespace AppInstaller::CLI SubContext(context.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); + } } PackageResourceObject Input; @@ -45,6 +83,11 @@ namespace AppInstaller::CLI SubContext->Args.AddArg(Execution::Args::Type::Source, Input.Source().value()); } + if (Input.Scope() && !Input.Scope().value().empty()) + { + SubContext->Args.AddArg(Execution::Args::Type::InstallScope, Input.Scope().value()); + } + *SubContext << Workflow::OpenSource() << Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(*SubContext)); @@ -52,6 +95,46 @@ namespace AppInstaller::CLI if (SubContext->IsTerminated()) { ParentContext.Terminate(SubContext->GetTerminationHR()); + return; + } + + // 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); + + 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; + auto installedPackage = package->GetInstalled(); + THROW_HR_IF(E_UNEXPECTED, !installedPackage); + auto installedVersion = installedPackage->GetLatestVersion(); + THROW_HR_IF(E_UNEXPECTED, !installedVersion); + auto metadata = installedVersion->GetMetadata(); + + // Fill Output and SubContext + Output.Exist(true); + Output.Identifier(package->GetProperty(PackageProperty::Id)); + Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); + + auto scopeItr = metadata.find(PackageVersionMetadata::InstalledScope); + if (scopeItr != metadata.end()) + { + Output.Scope(scopeItr->second); + } } } @@ -125,7 +208,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - PackageFunctionData data{ json }; + PackageFunctionData data{ context, json }; data.Get(); @@ -137,7 +220,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - PackageFunctionData data{ json }; + PackageFunctionData data{ context, json }; data.Get(); @@ -164,7 +247,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - PackageFunctionData data{ json }; + PackageFunctionData data{ context, json }; THROW_HR(E_NOTIMPL); } @@ -174,7 +257,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - PackageFunctionData data{ json }; + PackageFunctionData data{ context, json }; data.Get(); data.Output.InDesiredState(data.Test()); @@ -188,7 +271,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - PackageFunctionData data{ json }; + PackageFunctionData data{ context, json }; THROW_HR(E_NOTIMPL); } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index b09071f9ac..5656e2402e 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -230,9 +230,11 @@ namespace AppInstaller::CLI::Resource 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/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 2875f9878c..151c45a4e2 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3277,4 +3277,10 @@ Please specify one of them using the --source option to proceed. The install mode to use if needed. + + The target scope of the package. + + + Indicates whether to accept agreements for package installs. + \ No newline at end of file From fe3139e6cda15e3d2490bdf3b6b57b295093924f Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 10 Apr 2025 15:54:24 -0700 Subject: [PATCH 05/20] working on test; refactoring to use COM code for update availability --- .../Commands/DscPackageResource.cpp | 51 ++++++++++++++++++ src/AppInstallerCLICore/ExecutionReporter.cpp | 2 + .../PackageVersionSelection.cpp | 52 +++++++++++++++++++ .../Public/winget/PackageVersionSelection.h | 9 ++++ .../CatalogPackage.cpp | 2 +- 5 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index bd0804dd00..17fec988e6 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -179,6 +179,57 @@ namespace AppInstaller::CLI return result; } + + private: + 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 TestScope() + { + if (Input.Scope()) + { + if (Output.Scope()) + { + return Utility::ToLower(Input.Scope().value()) == Utility::ToLower(Output.Scope().value()); + } + else + { + return false; + } + } + else + { + return true; + } + } + + bool TestLatest() + { + if (Input.UseLatest()) + { + + } + else + { + return true; + } + } }; } diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 8515673bf4..bb0b7b92cd 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -48,6 +48,8 @@ namespace AppInstaller::CLI::Execution Reporter::Reporter(const Reporter& other, clone_t) : Reporter(other.m_out, other.m_in) { + SetChannel(other.m_channel); + if (other.m_style.has_value()) { SetStyle(*other.m_style); diff --git a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp index 7ecd8e17ea..c189ba2779 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,55 @@ namespace AppInstaller::Repository { return GetAvailablePackageFromSource(composite->GetAvailable(), sourceIdentifier); } + + DefaultInstallVersionData GetDefaultInstallVersion(const std::shared_ptr& composite) + { + using namespace AppInstaller::Pinning; + + DefaultInstallVersionData 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::CLI::Execution::COMContext context; + AppInstaller::Repository::IPackageVersion::Metadata installationMetadata = + installedVersion ? installedVersion->GetMetadata() : AppInstaller::Repository::IPackageVersion::Metadata{}; + AppInstaller::CLI::Workflow::ManifestComparator manifestComparator{ context, installationMetadata }; + + 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; + } + } } diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h index e9a5b5edbd..d6655e2905 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h @@ -25,4 +25,13 @@ 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 DefaultInstallVersionData + { + std::shared_ptr LatestApplicableVersion; + bool UpdateAvailable = false; + }; + + // Determines the default install version and whether an update is available. + DefaultInstallVersionData GetDefaultInstallVersion(const std::shared_ptr& composite); } diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp index 87da09d908..04308b78a2 100644 --- a/src/Microsoft.Management.Deployment/CatalogPackage.cpp +++ b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -74,8 +74,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation { using namespace AppInstaller::Pinning; - auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(m_package); auto installedVersion = AppInstaller::Repository::GetInstalledVersion(m_package); + auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(m_package, installedVersion); PinningData pinningData{ PinningData::Disposition::ReadOnly }; auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, installedVersion); From d2aff39ca7df7d10bccd56a3476012e256fcbb70 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 14 Apr 2025 21:06:49 -0700 Subject: [PATCH 06/20] Move ManifestComparator; package resource ready for set action impl --- src/AppInstallerCLI.sln | 47 ++-- .../AppInstallerCLICore.vcxproj | 2 - .../AppInstallerCLICore.vcxproj.filters | 6 - .../Commands/DscCommandBase.cpp | 4 +- .../Commands/DscComposableObject.h | 4 +- .../Commands/DscPackageResource.cpp | 211 +++++++++++---- .../Commands/DscPackageResource.h | 1 - .../Workflows/DependenciesFlow.cpp | 2 +- .../Workflows/DependencyNodeProcessor.cpp | 6 +- .../Workflows/RepairFlow.cpp | 2 +- .../Workflows/ShowFlow.cpp | 2 +- .../Workflows/UpdateFlow.cpp | 6 +- .../Workflows/WorkflowBase.cpp | 57 +++- .../Workflows/WorkflowBase.h | 4 + .../ManifestComparator.cpp | 117 ++++---- .../AppInstallerCommonCore.vcxproj | 4 + .../AppInstallerCommonCore.vcxproj.filters | 12 + .../Manifest}/ManifestComparator.cpp | 249 +++++++----------- .../Public/winget}/ManifestComparator.h | 56 ++-- .../Public/winget/StdErrLogger.h | 31 +++ src/AppInstallerCommonCore/StdErrLogger.cpp | 51 ++++ .../PackageVersionSelection.cpp | 78 +++++- .../Public/winget/PackageVersionSelection.h | 4 + .../CatalogPackage.cpp | 52 +--- .../PackageVersionInfo.cpp | 48 +++- 25 files changed, 675 insertions(+), 381 deletions(-) rename src/{AppInstallerCLICore/Workflows => AppInstallerCommonCore/Manifest}/ManifestComparator.cpp (72%) rename src/{AppInstallerCLICore/Workflows => AppInstallerCommonCore/Public/winget}/ManifestComparator.h (57%) create mode 100644 src/AppInstallerCommonCore/Public/winget/StdErrLogger.h create mode 100644 src/AppInstallerCommonCore/StdErrLogger.cpp diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 4e17ec49c5..bf7f1751b1 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -144,7 +144,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UndockedRegFreeWinRT", "Xla {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Detours", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\detours\detours.vcxproj", "{787EC629-C0FB-4BA9-9746-4A82CD06B73E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "detours", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\detours\detours.vcxproj", "{787EC629-C0FB-4BA9-9746-4A82CD06B73E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{8E43F982-40D5-4DF1-9044-C08047B5F43B}" ProjectSection(SolutionItems) = preProject @@ -505,21 +505,36 @@ Global {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.TestRelease|ARM64.ActiveCfg = Release|ARM64 {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.TestRelease|x64.ActiveCfg = Release|x64 {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.TestRelease|x86.ActiveCfg = Release|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.ActiveCfg = Debug - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.ActiveCfg = Debug - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.ActiveCfg = Debug - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|ARM64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x86.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.Build.0 = Debug|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.ActiveCfg = Debug|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.Build.0 = Debug|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.ActiveCfg = Debug|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.Build.0 = Debug|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.ActiveCfg = Fuzzing|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.Build.0 = Fuzzing|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.ActiveCfg = Release|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.Build.0 = Release|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.ActiveCfg = Release|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.Build.0 = Release|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.ActiveCfg = Release|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.Build.0 = Release|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.Build.0 = ReleaseStatic|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|ARM64.ActiveCfg = TestRelease|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|ARM64.Build.0 = TestRelease|ARM64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x64.ActiveCfg = TestRelease|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x64.Build.0 = TestRelease|x64 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x86.ActiveCfg = TestRelease|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x86.Build.0 = TestRelease|x86 {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM64.ActiveCfg = Debug|x64 {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.ActiveCfg = Debug|x64 {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.Build.0 = Debug|x64 diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index d327883e6b..6f74d6be81 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -370,7 +370,6 @@ - @@ -458,7 +457,6 @@ - diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 9b4ed71f2a..0004d827b1 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -47,9 +47,6 @@ Workflows - - Workflows - Workflows @@ -304,9 +301,6 @@ Source Files - - Workflows - Workflows diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp index 05b860fc5c..b2eb713633 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_)) \ diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 783e183483..6bbe5959fd 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -135,7 +135,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)); @@ -232,7 +232,7 @@ namespace AppInstaller::CLI 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, Resource::String::DscResourcePropertyDescriptionExist, {}, {}) - bool ShouldExist() { return m_value.value_or(true); } + bool ShouldExist() const { return m_value.value_or(true); } }; 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 index 17fec988e6..902f2a086a 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -5,6 +5,7 @@ #include "DscComposableObject.h" #include "Resources.h" #include "Workflows/WorkflowBase.h" +#include using namespace AppInstaller::Utility::literals; using namespace AppInstaller::Repository; @@ -16,7 +17,7 @@ namespace AppInstaller::CLI 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_FLAGS(ScopeProperty, std::string, Scope, "scope", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageScope, ({ "user", "machine" }), {}); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM_FLAGS(ScopeProperty, std::string, Scope, "scope", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageScope, ({ "user", "system" }), {}); WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM_FLAGS(MatchOptionProperty, std::string, MatchOption, "matchOption", DscComposablePropertyFlag::CopyToOutput, 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"); @@ -37,15 +38,15 @@ namespace AppInstaller::CLI { return MatchType::Exact; } - else if (lowerValue == "equalscaseinsensitive") + else if (lowerValue == "equals""case""insensitive") { return MatchType::CaseInsensitive; } - else if (lowerValue == "startswithcaseinsensitive") + else if (lowerValue == "starts""with""case""insensitive") { return MatchType::StartsWith; } - else if (lowerValue == "containscaseinsensitive") + else if (lowerValue == "contains""case""insensitive") { return MatchType::Substring; } @@ -53,14 +54,40 @@ namespace AppInstaller::CLI THROW_HR(E_INVALIDARG); } + std::string ConvertScope(std::string_view value, bool preferSystem) + { + std::string lowerValue = Utility::ToLower(value); + + if (lowerValue == "machine" || lowerValue == "system") + { + return preferSystem ? "system" : "machine"; + } + else + { + return std::string{ value }; + } + } + struct PackageFunctionData { PackageFunctionData(Execution::Context& context, const std::optional& json) : Input(json), - Output(Input.CopyForOutput()), - ParentContext(context), - SubContext(context.CreateSubContext()) + 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)) @@ -70,13 +97,8 @@ namespace AppInstaller::CLI } } - PackageResourceObject Input; - PackageResourceObject Output; - Execution::Context& ParentContext; - std::unique_ptr SubContext; - // Fills the Output object with the current state - void Get() + bool Get() { if (Input.Source()) { @@ -85,17 +107,17 @@ namespace AppInstaller::CLI if (Input.Scope() && !Input.Scope().value().empty()) { - SubContext->Args.AddArg(Execution::Args::Type::InstallScope, Input.Scope().value()); + SubContext->Args.AddArg(Execution::Args::Type::InstallScope, ConvertScope(Input.Scope().value(), false)); } *SubContext << Workflow::OpenSource() << - Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(*SubContext)); + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(*SubContext), false, CompositeSearchBehavior::AllPackages); if (SubContext->IsTerminated()) { ParentContext.Terminate(SubContext->GetTerminationHR()); - return; + return false; } // Do a manual search of the now opened source @@ -119,23 +141,53 @@ namespace AppInstaller::CLI else { auto& package = result.Matches.front().Package; + auto installedPackage = package->GetInstalled(); - THROW_HR_IF(E_UNEXPECTED, !installedPackage); - auto installedVersion = installedPackage->GetLatestVersion(); - THROW_HR_IF(E_UNEXPECTED, !installedVersion); - auto metadata = installedVersion->GetMetadata(); // Fill Output and SubContext - Output.Exist(true); + Output.Exist(static_cast(installedPackage)); Output.Identifier(package->GetProperty(PackageProperty::Id)); - Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); - auto scopeItr = metadata.find(PackageVersionMetadata::InstalledScope); - if (scopeItr != metadata.end()) + if (installedPackage) { - Output.Scope(scopeItr->second); + auto installedVersion = installedPackage->GetLatestVersion(); + THROW_HR_IF(E_UNEXPECTED, !installedVersion); + auto metadata = installedVersion->GetMetadata(); + + Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); + + auto scopeItr = metadata.find(PackageVersionMetadata::InstalledScope); + if (scopeItr != metadata.end()) + { + Output.Scope(ConvertScope(scopeItr->second, true)); + } + + auto data = Repository::GetDefaultInstallVersion(package); + Output.UseLatest(!data.UpdateAvailable); } } + + return true; + } + + void Uninstall() + { + THROW_HR(E_NOTIMPL); + } + + void Update() + { + THROW_HR(E_NOTIMPL); + } + + void Install() + { + THROW_HR(E_NOTIMPL); + } + + void Reinstall() + { + THROW_HR(E_NOTIMPL); } // Determines if the current Output values match the Input values state. @@ -148,7 +200,7 @@ namespace AppInstaller::CLI { if (Output.Exist().value()) { - THROW_HR(E_NOTIMPL); + return TestVersion() && TestScope() && TestLatest(); } else { @@ -174,13 +226,25 @@ namespace AppInstaller::CLI } else { - THROW_HR(E_NOTIMPL); + if (!TestVersion()) + { + result.append(std::string{ VersionProperty::Name() }); + } + + if (!TestScope()) + { + result.append(std::string{ ScopeProperty::Name() }); + } + + if (!TestLatest()) + { + result.append(std::string{ UseLatestProperty::Name() }); + } } return result; } - private: bool TestVersion() { if (Input.Version()) @@ -206,7 +270,8 @@ namespace AppInstaller::CLI { if (Output.Scope()) { - return Utility::ToLower(Input.Scope().value()) == Utility::ToLower(Output.Scope().value()); + return Manifest::ConvertToScopeEnum(ConvertScope(Input.Scope().value(), false)) == + Manifest::ConvertToScopeEnum(ConvertScope(Output.Scope().value(), false)); } else { @@ -221,9 +286,16 @@ namespace AppInstaller::CLI bool TestLatest() { - if (Input.UseLatest()) + if (Input.UseLatest() && Input.UseLatest().value()) { - + if (Output.UseLatest()) + { + return Output.UseLatest().value(); + } + else + { + return false; + } } else { @@ -235,7 +307,7 @@ namespace AppInstaller::CLI DscPackageResource::DscPackageResource(std::string_view parent) : DscCommandBase(parent, "package", DscResourceKind::Resource, - DscFunctions::Get | DscFunctions::Set | DscFunctions::WhatIf | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) { } @@ -261,7 +333,10 @@ namespace AppInstaller::CLI { PackageFunctionData data{ context, json }; - data.Get(); + if (!data.Get()) + { + return; + } WriteJsonOutputLine(context, data.Output.ToJson()); } @@ -273,20 +348,64 @@ namespace AppInstaller::CLI { PackageFunctionData data{ context, json }; - data.Get(); + if (!data.Get()) + { + return; + } if (!data.Test()) { - THROW_HR(E_NOTIMPL); + if (data.Input.ShouldExist()) + { + if (data.Output.Exist().value()) + { + if (!data.TestScope()) + { + data.Reinstall(); + } + if (!data.TestLatest()) + { + data.Update(); + } + else // (!data.TestVersion()) + { + Utility::Version inputVersion{ data.Input.Version().value() }; + Utility::Version outputVersion{ data.Output.Version().value() }; + + if (outputVersion < inputVersion) + { + data.Update(); + } + else + { + data.Reinstall(); + } + } + } + else + { + data.Install(); + } + } + else + { + data.Uninstall(); + } + + if (data.SubContext->IsTerminated()) + { + data.ParentContext.Terminate(data.SubContext->GetTerminationHR()); + return; + } } // Capture the diff before updating the output auto diff = data.DiffJson(); - data.Output.Exist(data.Input.ShouldExist()); - if (data.Output.Exist().value()) + data.Reset(); + if (!data.Get()) { - THROW_HR(E_NOTIMPL); + return; } WriteJsonOutputLine(context, data.Output.ToJson()); @@ -294,23 +413,17 @@ namespace AppInstaller::CLI } } - void DscPackageResource::ResourceFunctionWhatIf(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - PackageFunctionData data{ context, json }; - - THROW_HR(E_NOTIMPL); - } - } - void DscPackageResource::ResourceFunctionTest(Execution::Context& context) const { if (auto json = GetJsonFromInput(context)) { PackageFunctionData data{ context, json }; - data.Get(); + if (!data.Get()) + { + return; + } + data.Output.InDesiredState(data.Test()); WriteJsonOutputLine(context, data.Output.ToJson()); diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.h b/src/AppInstallerCLICore/Commands/DscPackageResource.h index 919cacd37e..3bc0787096 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.h +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.h @@ -18,7 +18,6 @@ namespace AppInstaller::CLI void ResourceFunctionGet(Execution::Context& context) const override; void ResourceFunctionSet(Execution::Context& context) const override; - void ResourceFunctionWhatIf(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/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 99d0a82e04..24d3fb3439 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; diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 44aa57650d..55ca45972d 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.CurrentlyInstalledScope = 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/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 c189ba2779..d87b5ed550 100644 --- a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp +++ b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp @@ -168,10 +168,12 @@ namespace AppInstaller::Repository 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 }; + 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) @@ -205,5 +207,73 @@ namespace AppInstaller::Repository 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 d6655e2905..634f64a783 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 @@ -34,4 +35,7 @@ namespace AppInstaller::Repository // Determines the default install version and whether an update is available. DefaultInstallVersionData GetDefaultInstallVersion(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/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp index 04308b78a2..d139c92f8f 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_defaultInstallVersionOnceFlag, [&]() { - using namespace AppInstaller::Pinning; + auto data = AppInstaller::Repository::GetDefaultInstallVersion(m_package); - auto installedVersion = AppInstaller::Repository::GetInstalledVersion(m_package); - auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(m_package, installedVersion); + 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_defaultInstallVersion = *latestVersionImpl; } }); diff --git a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp index 6ac3350fd4..1c729e5a7e 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); @@ -133,20 +169,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); From 4f3f27afebd362293326ebfc5a2910de6644bfef Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 15 Apr 2025 16:13:24 -0700 Subject: [PATCH 07/20] package resource CC; working on making input better --- .../Commands/DscCommandBase.cpp | 26 ++- .../Commands/DscCommandBase.h | 2 +- .../Commands/DscComposableObject.h | 17 +- .../Commands/DscPackageResource.cpp | 153 +++++++++++++++--- src/AppInstallerCLICore/ExecutionReporter.cpp | 19 +++ src/AppInstallerCLICore/ExecutionReporter.h | 4 + 6 files changed, 180 insertions(+), 41 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp index b2eb713633..7f3b137e5d 100644 --- a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp @@ -266,18 +266,30 @@ 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()) { + Json::Value result; + Json::CharReaderBuilder builder; + Json::String errors; + if (Json::parseFromStream(builder, context.Reporter.RawInputStream(), &result, &errors)) + { + 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.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 6bbe5959fd..ec7f13faa0 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -114,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. @@ -157,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) { @@ -165,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()); } diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 902f2a086a..cefdc86da1 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -5,6 +5,10 @@ #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; @@ -70,8 +74,8 @@ namespace AppInstaller::CLI struct PackageFunctionData { - PackageFunctionData(Execution::Context& context, const std::optional& json) : - Input(json), + PackageFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) : + Input(json, ignoreFieldRequirements), ParentContext(context) { Reset(); @@ -97,8 +101,7 @@ namespace AppInstaller::CLI } } - // Fills the Output object with the current state - bool Get() + void PrepareSubContextInputs() { if (Input.Source()) { @@ -109,8 +112,15 @@ namespace AppInstaller::CLI { SubContext->Args.AddArg(Execution::Args::Type::InstallScope, ConvertScope(Input.Scope().value(), false)); } + } + + // 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); @@ -128,6 +138,7 @@ namespace AppInstaller::CLI request.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, Input.Identifier().value())); SearchResult result = source.Search(request); + SubContext->Add(result); if (result.Matches.empty()) { @@ -141,6 +152,7 @@ namespace AppInstaller::CLI else { auto& package = result.Matches.front().Package; + SubContext->Add(package); auto installedPackage = package->GetInstalled(); @@ -172,22 +184,77 @@ namespace AppInstaller::CLI void Uninstall() { - THROW_HR(E_NOTIMPL); - } + if (Input.Version()) + { + SubContext->Args.AddArg(Execution::Args::Type::TargetVersion, Input.Version().value()); + } + else + { + SubContext->Args.AddArg(Execution::Args::Type::AllVersions); + } - void Update() - { - THROW_HR(E_NOTIMPL); + *SubContext << + Workflow::UninstallSinglePackage; + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + return; + } + + Output.Exist(false); + Output.Version(std::nullopt); + Output.Scope(std::nullopt); + Output.UseLatest(std::nullopt); } void Install() { - THROW_HR(E_NOTIMPL); + if (Input.Version()) + { + SubContext->Args.AddArg(Execution::Args::Type::Version, Input.Version().value()); + } + + *SubContext << + Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install) << + 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.Scope(std::nullopt); + if (SubContext->Contains(Execution::Data::Installer)) + { + const auto& installer = SubContext->Get(); + if (installer) + { + Output.Scope(ConvertScope(Manifest::ScopeToString(installer->Scope), true)); + } + } + + Output.UseLatest(std::nullopt); } void Reinstall() { - THROW_HR(E_NOTIMPL); + SubContext->Args.AddArg(Execution::Args::Type::UninstallPrevious); + + Install(); } // Determines if the current Output values match the Input values state. @@ -353,6 +420,9 @@ namespace AppInstaller::CLI return; } + // Capture the diff before updating the output + auto diff = data.DiffJson(); + if (!data.Test()) { if (data.Input.ShouldExist()) @@ -365,7 +435,8 @@ namespace AppInstaller::CLI } if (!data.TestLatest()) { - data.Update(); + // Install will swap to update flow + data.Install(); } else // (!data.TestVersion()) { @@ -374,7 +445,8 @@ namespace AppInstaller::CLI if (outputVersion < inputVersion) { - data.Update(); + // Install will swap to update flow + data.Install(); } else { @@ -394,20 +466,10 @@ namespace AppInstaller::CLI if (data.SubContext->IsTerminated()) { - data.ParentContext.Terminate(data.SubContext->GetTerminationHR()); return; } } - // Capture the diff before updating the output - auto diff = data.DiffJson(); - - data.Reset(); - if (!data.Get()) - { - return; - } - WriteJsonOutputLine(context, data.Output.ToJson()); WriteJsonOutputLine(context, diff); } @@ -433,11 +495,50 @@ namespace AppInstaller::CLI void DscPackageResource::ResourceFunctionExport(Execution::Context& context) const { - if (auto json = GetJsonFromInput(context)) + auto json = GetJsonFromInput(context, false); + PackageFunctionData data{ context, json, true }; + + data.PrepareSubContextInputs(); + + if (!data.Input.UseLatest().value_or(true)) { - PackageFunctionData data{ context, json }; + 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()); + } - THROW_HR(E_NOTIMPL); + if (package.Scope != Manifest::ScopeEnum::Unknown) + { + output.Scope(ConvertScope(Manifest::ScopeToString(package.Scope), true)); + } + + WriteJsonOutputLine(context, output.ToJson()); + } } } diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index bb0b7b92cd..fbf75e8729 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -23,6 +23,20 @@ namespace AppInstaller::CLI::Execution const Sequence& ConfigurationUnitEmphasis = TextFormat::Foreground::BrightCyan; const Sequence& AuthenticationEmphasis = TextFormat::Foreground::BrightYellow; + Reporter::Reporter() : + Reporter(std::cout, std::cin) + { + HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE); + if (outHandle == INVALID_HANDLE_VALUE) + { + LOG_LAST_ERROR(); + } + else if (outHandle != NULL) + { + + } + } + Reporter::Reporter(std::ostream& outStream, std::istream& inStream) : Reporter(std::make_shared(outStream, true, ConsoleModeRestore::Instance().IsVTEnabled()), inStream) { @@ -138,6 +152,11 @@ namespace AppInstaller::CLI::Execution return m_in; } + bool Reporter::InputStreamIsInteractive() const + { + + } + 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..632e907853 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); From 9e2c1117a08e5f785b54fcf8648768fc43a8cbe9 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 16 Apr 2025 10:42:52 -0700 Subject: [PATCH 08/20] Use input handle type to determine if we should attempt to read from it --- src/AppInstallerCLICore/Core.cpp | 2 +- src/AppInstallerCLICore/ExecutionContext.h | 1 + src/AppInstallerCLICore/ExecutionReporter.cpp | 30 +++++++++++++------ src/AppInstallerCLICore/ExecutionReporter.h | 2 ++ 4 files changed, 25 insertions(+), 10 deletions(-) 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/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 fbf75e8729..5de04d9acc 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -23,20 +23,29 @@ namespace AppInstaller::CLI::Execution const Sequence& ConfigurationUnitEmphasis = TextFormat::Foreground::BrightCyan; const Sequence& AuthenticationEmphasis = TextFormat::Foreground::BrightYellow; - Reporter::Reporter() : - Reporter(std::cout, std::cin) + namespace { - HANDLE outHandle = GetStdHandle(STD_OUTPUT_HANDLE); - if (outHandle == INVALID_HANDLE_VALUE) - { - LOG_LAST_ERROR(); - } - else if (outHandle != NULL) + 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) { @@ -62,6 +71,9 @@ 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()) @@ -154,7 +166,7 @@ namespace AppInstaller::CLI::Execution bool Reporter::InputStreamIsInteractive() const { - + return m_inStreamFileType == FILE_TYPE_CHAR; } bool Reporter::PromptForBoolResponse(Resource::LocString message, Level level, bool resultIfDisabled) diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index 632e907853..20babda255 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -208,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; From 7e4c8c8a93125a403855b277deb2c48ea357570d Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 16 Apr 2025 14:06:41 -0700 Subject: [PATCH 09/20] Change name of refactored method to match updates --- src/AppInstallerCLICore/Commands/DscPackageResource.cpp | 2 +- src/AppInstallerRepositoryCore/PackageVersionSelection.cpp | 4 ++-- .../Public/winget/PackageVersionSelection.h | 4 ++-- src/Microsoft.Management.Deployment/CatalogPackage.cpp | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index cefdc86da1..88c5bf5319 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -174,7 +174,7 @@ namespace AppInstaller::CLI Output.Scope(ConvertScope(scopeItr->second, true)); } - auto data = Repository::GetDefaultInstallVersion(package); + auto data = Repository::GetLatestApplicableVersion(package); Output.UseLatest(!data.UpdateAvailable); } } diff --git a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp index d87b5ed550..77cd3c7ac7 100644 --- a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp +++ b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp @@ -156,11 +156,11 @@ namespace AppInstaller::Repository return GetAvailablePackageFromSource(composite->GetAvailable(), sourceIdentifier); } - DefaultInstallVersionData GetDefaultInstallVersion(const std::shared_ptr& composite) + LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr& composite) { using namespace AppInstaller::Pinning; - DefaultInstallVersionData result; + LatestApplicableVersionData result; auto installedVersion = AppInstaller::Repository::GetInstalledVersion(composite); auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(composite, installedVersion); diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h index 634f64a783..0497646015 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h @@ -27,14 +27,14 @@ 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 DefaultInstallVersionData + struct LatestApplicableVersionData { std::shared_ptr LatestApplicableVersion; bool UpdateAvailable = false; }; // Determines the default install version and whether an update is available. - DefaultInstallVersionData GetDefaultInstallVersion(const std::shared_ptr& composite); + 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/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp index ff2c3a4d21..e83eaa9975 100644 --- a/src/Microsoft.Management.Deployment/CatalogPackage.cpp +++ b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -71,7 +71,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation std::call_once(m_latestApplicableVersionOnceFlag, [&]() { - auto data = AppInstaller::Repository::GetDefaultInstallVersion(m_package); + auto data = AppInstaller::Repository::GetLatestApplicableVersion(m_package); m_updateAvailable = data.UpdateAvailable; @@ -97,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 { From e5220952b090e828dc5caaad9612466e6f1fb5f8 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 16 Apr 2025 14:19:01 -0700 Subject: [PATCH 10/20] Revert solution change --- src/AppInstallerCLI.sln | 47 ++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index bf7f1751b1..4e17ec49c5 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -144,7 +144,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UndockedRegFreeWinRT", "Xla {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "detours", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\detours\detours.vcxproj", "{787EC629-C0FB-4BA9-9746-4A82CD06B73E}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Detours", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\detours\detours.vcxproj", "{787EC629-C0FB-4BA9-9746-4A82CD06B73E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{8E43F982-40D5-4DF1-9044-C08047B5F43B}" ProjectSection(SolutionItems) = preProject @@ -505,36 +505,21 @@ Global {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.TestRelease|ARM64.ActiveCfg = Release|ARM64 {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.TestRelease|x64.ActiveCfg = Release|x64 {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.TestRelease|x86.ActiveCfg = Release|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.Build.0 = Debug|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.ActiveCfg = Debug|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.Build.0 = Debug|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.ActiveCfg = Debug|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.Build.0 = Debug|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.Build.0 = Fuzzing|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.ActiveCfg = Fuzzing|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.Build.0 = Fuzzing|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.ActiveCfg = Release|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.Build.0 = Release|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.ActiveCfg = Release|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.Build.0 = Release|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.ActiveCfg = Release|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.Build.0 = Release|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.Build.0 = ReleaseStatic|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|ARM64.ActiveCfg = TestRelease|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|ARM64.Build.0 = TestRelease|ARM64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x64.ActiveCfg = TestRelease|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x64.Build.0 = TestRelease|x64 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x86.ActiveCfg = TestRelease|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x86.Build.0 = TestRelease|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.TestRelease|x86.ActiveCfg = Release {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM64.ActiveCfg = Debug|x64 {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.ActiveCfg = Debug|x64 {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.Build.0 = Debug|x64 From 468b6d5427f6ca62c574dbe997c60ae828b203ff Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 16 Apr 2025 19:40:08 -0700 Subject: [PATCH 11/20] Add E2E tests for package resource --- .../Commands/DscPackageResource.cpp | 2 +- .../DSCv3ResourceCommands.cs | 747 ++++++++++++++++++ .../Manifests/TestExeInstaller.1.0.1.0.yaml | 15 + .../Manifests/TestExeInstaller.1.1.0.0.yaml | 15 + .../Manifests/TestExeInstaller.2.0.0.0.yaml | 15 + .../TestData/Manifests/TestExeInstaller.yaml | 15 + 6 files changed, 808 insertions(+), 1 deletion(-) create mode 100644 src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 88c5bf5319..e62090b3fa 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -241,7 +241,7 @@ namespace AppInstaller::CLI if (SubContext->Contains(Execution::Data::Installer)) { const auto& installer = SubContext->Get(); - if (installer) + if (installer && installer->Scope != Manifest::ScopeEnum::Unknown) { Output.Scope(ConvertScope(Manifest::ScopeToString(installer->Scope), true)); } diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs new file mode 100644 index 0000000000..3a86607885 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -0,0 +1,747 @@ +// ----------------------------------------------------------------------------- +// +// 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 = "AppInstallerTest.TestExeInstaller"; + 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 UserScope = "user"; + private const string SystemScope = "system"; + 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"; + private const string ScopePropertyName = "scope"; + + /// + /// 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() + { + WinGetSettingsHelper.ConfigureFeature("dsc3", true); + EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + WinGetSettingsHelper.ConfigureFeature("dsc3", false); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + // Try clean up TestExeInstaller for failure cases where cleanup is not successful + TestCommon.RunAICLICommand("uninstall", DefaultPackageIdentifier); + } + + /// + /// 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, + Scope = UserScope, + 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 `test` on the `package` resource with the scope that is present. + /// + [Test] + public void Package_Test_ScopeMatch() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Scope = UserScope, + }; + + 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 the scope that is not present. + /// + [Test] + public void Package_Test_ScopeMismatch() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Scope = SystemScope, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ ScopePropertyName ]); + } + + /// + /// 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); + + 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 with a different scope from the one currently installed. + /// + [Test] + public void Package_Set_ChangeScope() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Scope = SystemScope, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, SystemScope); + + AssertDiffState(diff, [ ScopePropertyName ]); + + // 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, SystemScope); + } + + /// + /// 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); + + 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); + + 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); + + 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, null); + 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); + 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 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) + { + return value == 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, string scope = UserScope) + { + Assert.IsNotNull(output); + Assert.True(output.Exist); + Assert.AreEqual(DefaultPackageIdentifier, output.Identifier); + Assert.AreEqual(version, output.Version); + Assert.AreEqual(scope, output.Scope.ToLower()); + 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 Scope { 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/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 From 7f9820e62bd47700ecbd6049cd44644f5c8df65f Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 17 Apr 2025 11:28:39 -0700 Subject: [PATCH 12/20] Fix copy-pasta property name; use package resource to ensure cleanup --- src/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 2 +- src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs | 9 ++++++++- .../AppInstallerCLITests.vcxproj.filters | 8 ++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 55ca45972d..4f97bbf402 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -493,7 +493,7 @@ namespace AppInstaller::CLI::Workflow if (context.Args.Contains(Execution::Args::Type::InstallScope)) { - options.CurrentlyInstalledScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + options.RequestedInstallerScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); } if (context.Contains(Execution::Data::AllowUnknownScope)) diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 3a86607885..71fd0331b0 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -75,7 +75,14 @@ public void OneTimeTeardown() public void Setup() { // Try clean up TestExeInstaller for failure cases where cleanup is not successful - TestCommon.RunAICLICommand("uninstall", DefaultPackageIdentifier); + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Exist = false, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); } /// 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 From 2b46b163551e7cf588a1376e5a6dac6798590e70 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 17 Apr 2025 15:26:41 -0700 Subject: [PATCH 13/20] Test fixes --- .../DSCv3ResourceCommands.cs | 4 ++-- .../DownloadCommand.cs | 4 ++-- .../Interop/DownloadInterop.cs | 4 ++-- .../Interop/InstallInterop.cs | 6 +++--- .../Manifests/TestExeInstaller_NoScope.yaml | 20 +++++++++++++++++++ 5 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 71fd0331b0..e9be139d68 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -21,7 +21,7 @@ namespace AppInstallerCLIE2ETests [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 = "AppInstallerTest.TestExeInstaller"; + 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"; @@ -748,7 +748,7 @@ private class PackageResourceData public string InstallMode { get; set; } - public bool? AcceptAgreements { get; set; } + public bool? AcceptAgreements { get; set; } = true; } } } 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/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_NoScope.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml new file mode 100644 index 0000000000..b9930f174c --- /dev/null +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml @@ -0,0 +1,20 @@ +Id: AppInstallerTest.TestExeInstallerNoScope +Name: TestExeInstaller +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 From 1ad6b708192d90976b13f02454fd248828fc4194 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 21 Apr 2025 09:35:49 -0700 Subject: [PATCH 14/20] Test fixes --- src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs | 4 ++-- .../TestData/Manifests/TestExeInstaller_NoScope.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index e9be139d68..894465c40f 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -19,7 +19,7 @@ namespace AppInstallerCLIE2ETests /// [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 + public class DSCv3ResourceCommands : BaseCommand { private const string DefaultPackageIdentifier = Constants.ExeInstallerPackageId; private const string DefaultPackageLowVersion = "1.0.0.0"; @@ -748,7 +748,7 @@ private class PackageResourceData public string InstallMode { get; set; } - public bool? AcceptAgreements { get; set; } = true; + public bool? AcceptAgreements { get; set; } } } } diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml index b9930f174c..92ada3f407 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml @@ -1,5 +1,5 @@ Id: AppInstallerTest.TestExeInstallerNoScope -Name: TestExeInstaller +Name: TestExeInstallerNoScope Version: 1.0.0.0 Publisher: AppInstallerTest License: Test From 62f6aee8dca091228b278b16946697e77a9d6d91 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 21 Apr 2025 13:47:35 -0700 Subject: [PATCH 15/20] Add more logging; add correlation param --- src/AppInstallerCLICore/Argument.cpp | 5 +++++ .../Commands/DscCommandBase.cpp | 1 + .../Commands/DscPackageResource.cpp | 16 ++++++++++++++++ src/AppInstallerCLICore/ExecutionArgs.h | 1 + src/AppInstallerCLICore/Resources.h | 1 + .../DSCv3ResourceCommands.cs | 4 ++-- .../Helpers/TestCommon.cs | 6 ++++-- .../Shared/Strings/en-us/winget.resw | 3 +++ 8 files changed, 33 insertions(+), 4 deletions(-) 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/DscCommandBase.cpp b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp index 7f3b137e5d..4b655ce70f 100644 --- a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp @@ -276,6 +276,7 @@ namespace AppInstaller::CLI 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; } diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index e62090b3fa..200485291f 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -179,11 +179,14 @@ namespace AppInstaller::CLI } } + 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()); @@ -210,6 +213,8 @@ namespace AppInstaller::CLI void Install() { + AICLI_LOG(CLI, Verbose, << "Package::Install invoked"); + if (Input.Version()) { SubContext->Args.AddArg(Execution::Args::Type::Version, Input.Version().value()); @@ -252,6 +257,8 @@ namespace AppInstaller::CLI void Reinstall() { + AICLI_LOG(CLI, Verbose, << "Package::Reinstall invoked"); + SubContext->Args.AddArg(Execution::Args::Type::UninstallPrevious); Install(); @@ -267,15 +274,18 @@ namespace AppInstaller::CLI { if (Output.Exist().value()) { + AICLI_LOG(CLI, Verbose, << "Package::Test needed to inspect these properties: Version(" << TestVersion() << "), Scope(" << TestScope() << "), Latest(" << TestLatest() << ")"); return TestVersion() && TestScope() && 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(); } } @@ -431,11 +441,13 @@ namespace AppInstaller::CLI { if (!data.TestScope()) { + AICLI_LOG(CLI, Info, << "Reinstalling package to change scope"); data.Reinstall(); } if (!data.TestLatest()) { // Install will swap to update flow + AICLI_LOG(CLI, Info, << "Installing package to update to latest"); data.Install(); } else // (!data.TestVersion()) @@ -446,21 +458,25 @@ namespace AppInstaller::CLI 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(); } 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/Resources.h b/src/AppInstallerCLICore/Resources.h index 5656e2402e..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); diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 894465c40f..4aeffb5797 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -576,7 +576,7 @@ public void Package_Export_NoInput() var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); Assert.AreEqual(0, setupInstall.ExitCode); - var result = RunDSCv3Command(PackageResource, ExportFunction, null); + var result = RunDSCv3Command(PackageResource, ExportFunction, null, 300000); AssertSuccessfulResourceRun(ref result); List output = GetOutputLinesAs(result.StdOut); @@ -609,7 +609,7 @@ public void Package_Export_RequestVersions() UseLatest = false, }; - var result = RunDSCv3Command(PackageResource, ExportFunction, packageResourceData); + var result = RunDSCv3Command(PackageResource, ExportFunction, packageResourceData, 300000); AssertSuccessfulResourceRun(ref result); List output = GetOutputLinesAs(result.StdOut); diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 5e8946b82a..289648e343 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -115,16 +115,18 @@ 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(); + 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/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 151c45a4e2..7f9b635704 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3283,4 +3283,7 @@ Please specify one of them using the --source option to proceed. Indicates whether to accept agreements for package installs. + + An unused argument for logging + \ No newline at end of file From b80d0e7e616a110d36eada4a55f4b8685cb257fc Mon Sep 17 00:00:00 2001 From: John McPherson Date: Mon, 21 Apr 2025 20:11:23 -0700 Subject: [PATCH 16/20] Work on test fixes --- .../Commands/DscCommandBase.cpp | 2 + .../Commands/DscPackageResource.cpp | 47 +++++++++++++----- .../DSCv3ResourceCommands.cs | 48 ++++++++++++------- .../Helpers/TestCommon.cs | 9 ++++ .../Helpers/WinGetSettingsHelper.cs | 20 ++++++++ src/AppInstallerTestExeInstaller/main.cpp | 2 +- 6 files changed, 99 insertions(+), 29 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp index 4b655ce70f..6cd2dd6773 100644 --- a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp @@ -271,6 +271,8 @@ namespace AppInstaller::CLI // 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; diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 200485291f..85d22c3589 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -162,16 +162,41 @@ namespace AppInstaller::CLI if (installedPackage) { - auto installedVersion = installedPackage->GetLatestVersion(); - THROW_HR_IF(E_UNEXPECTED, !installedVersion); - auto metadata = installedVersion->GetMetadata(); + auto versionKeys = installedPackage->GetVersionKeys(); + AICLI_LOG(CLI, Verbose, << "Package::Get found " << versionKeys.size() << " installed versions"); - Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); + std::shared_ptr installedVersion; - auto scopeItr = metadata.find(PackageVersionMetadata::InstalledScope); - if (scopeItr != metadata.end()) + // Find the specific version provided if possible + if (Input.Version()) { - Output.Scope(ConvertScope(scopeItr->second, true)); + 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 metadata = installedVersion->GetMetadata(); + auto scopeItr = metadata.find(PackageVersionMetadata::InstalledScope); + if (scopeItr != metadata.end()) + { + Output.Scope(ConvertScope(scopeItr->second, true)); + } } auto data = Repository::GetLatestApplicableVersion(package); @@ -548,10 +573,10 @@ namespace AppInstaller::CLI output.Version(package.VersionAndChannel.GetVersion().ToString()); } - if (package.Scope != Manifest::ScopeEnum::Unknown) - { - output.Scope(ConvertScope(Manifest::ScopeToString(package.Scope), true)); - } + // 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()); } diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 4aeffb5797..7a44c8fdef 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -19,7 +19,7 @@ namespace AppInstallerCLIE2ETests /// [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 : BaseCommand + public class DSCv3ResourceCommands { private const string DefaultPackageIdentifier = Constants.ExeInstallerPackageId; private const string DefaultPackageLowVersion = "1.0.0.0"; @@ -55,7 +55,9 @@ public static void EnsureTestResourcePresence() [OneTimeSetUp] public void OneTimeSetup() { + TestCommon.SetupTestSource(); WinGetSettingsHelper.ConfigureFeature("dsc3", true); + WinGetSettingsHelper.ConfigureLoggingLevel("verbose"); EnsureTestResourcePresence(); } @@ -65,7 +67,10 @@ public void OneTimeSetup() [OneTimeTearDown] public void OneTimeTeardown() { + RemoveTestPackage(); + WinGetSettingsHelper.ConfigureLoggingLevel(null); WinGetSettingsHelper.ConfigureFeature("dsc3", false); + TestCommon.TearDownTestSource(); } /// @@ -75,14 +80,7 @@ public void OneTimeTeardown() public void Setup() { // Try clean up TestExeInstaller for failure cases where cleanup is not successful - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Exist = false, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); + RemoveTestPackage(); } /// @@ -442,7 +440,7 @@ public void Package_Set_ChangeScope() AssertSuccessfulResourceRun(ref result); (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion, SystemScope); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, SystemScope, ignoreLatest: true); AssertDiffState(diff, [ ScopePropertyName ]); @@ -632,6 +630,18 @@ public void Package_Export_RequestVersions() 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); @@ -697,20 +707,24 @@ private static List GetOutputLinesAs(string output) return result; } - private static void AssertExistingPackageResourceData(PackageResourceData output, string version, string scope = UserScope) + private static void AssertExistingPackageResourceData(PackageResourceData output, string version, string scope = UserScope, bool ignoreLatest = false) { Assert.IsNotNull(output); Assert.True(output.Exist); Assert.AreEqual(DefaultPackageIdentifier, output.Identifier); Assert.AreEqual(version, output.Version); Assert.AreEqual(scope, output.Scope.ToLower()); - if (version == DefaultPackageHighVersion) - { - Assert.True(output.UseLatest); - } - else + + if (!ignoreLatest) { - Assert.False(output.UseLatest); + if (version == DefaultPackageHighVersion) + { + Assert.True(output.UseLatest); + } + else + { + Assert.False(output.UseLatest); + } } } diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 289648e343..4afe1b2ceb 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -117,6 +117,15 @@ public static RunCommandResult RunAICLICommand(string command, string parameters { 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 + 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/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; } From 073d30c4b4fb29561c454bb66bab23d39c4877d3 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 22 Apr 2025 14:10:19 -0700 Subject: [PATCH 17/20] Remove scope from resource --- .../Commands/DscPackageResource.cpp | 81 ++-------------- .../DSCv3ResourceCommands.cs | 95 +------------------ .../Shared/Strings/en-us/winget.resw | 2 +- 3 files changed, 11 insertions(+), 167 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 85d22c3589..1dc1ea2cd2 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -21,13 +21,17 @@ namespace AppInstaller::CLI 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_FLAGS(ScopeProperty, std::string, Scope, "scope", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageScope, ({ "user", "system" }), {}); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM_FLAGS(MatchOptionProperty, std::string, MatchOption, "matchOption", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageMatchOption, ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); + 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); - using PackageResourceObject = DscComposableObject; + // 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) { @@ -58,20 +62,6 @@ namespace AppInstaller::CLI THROW_HR(E_INVALIDARG); } - std::string ConvertScope(std::string_view value, bool preferSystem) - { - std::string lowerValue = Utility::ToLower(value); - - if (lowerValue == "machine" || lowerValue == "system") - { - return preferSystem ? "system" : "machine"; - } - else - { - return std::string{ value }; - } - } - struct PackageFunctionData { PackageFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) : @@ -107,11 +97,6 @@ namespace AppInstaller::CLI { SubContext->Args.AddArg(Execution::Args::Type::Source, Input.Source().value()); } - - if (Input.Scope() && !Input.Scope().value().empty()) - { - SubContext->Args.AddArg(Execution::Args::Type::InstallScope, ConvertScope(Input.Scope().value(), false)); - } } // Fills the Output object with the current state @@ -190,13 +175,6 @@ namespace AppInstaller::CLI if (installedVersion) { Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); - - auto metadata = installedVersion->GetMetadata(); - auto scopeItr = metadata.find(PackageVersionMetadata::InstalledScope); - if (scopeItr != metadata.end()) - { - Output.Scope(ConvertScope(scopeItr->second, true)); - } } auto data = Repository::GetLatestApplicableVersion(package); @@ -232,7 +210,6 @@ namespace AppInstaller::CLI Output.Exist(false); Output.Version(std::nullopt); - Output.Scope(std::nullopt); Output.UseLatest(std::nullopt); } @@ -267,16 +244,6 @@ namespace AppInstaller::CLI } } - Output.Scope(std::nullopt); - if (SubContext->Contains(Execution::Data::Installer)) - { - const auto& installer = SubContext->Get(); - if (installer && installer->Scope != Manifest::ScopeEnum::Unknown) - { - Output.Scope(ConvertScope(Manifest::ScopeToString(installer->Scope), true)); - } - } - Output.UseLatest(std::nullopt); } @@ -299,8 +266,8 @@ namespace AppInstaller::CLI { if (Output.Exist().value()) { - AICLI_LOG(CLI, Verbose, << "Package::Test needed to inspect these properties: Version(" << TestVersion() << "), Scope(" << TestScope() << "), Latest(" << TestLatest() << ")"); - return TestVersion() && TestScope() && TestLatest(); + AICLI_LOG(CLI, Verbose, << "Package::Test needed to inspect these properties: Version(" << TestVersion() << "), Latest(" << TestLatest() << ")"); + return TestVersion() && TestLatest(); } else { @@ -333,11 +300,6 @@ namespace AppInstaller::CLI result.append(std::string{ VersionProperty::Name() }); } - if (!TestScope()) - { - result.append(std::string{ ScopeProperty::Name() }); - } - if (!TestLatest()) { result.append(std::string{ UseLatestProperty::Name() }); @@ -366,26 +328,6 @@ namespace AppInstaller::CLI } } - bool TestScope() - { - if (Input.Scope()) - { - if (Output.Scope()) - { - return Manifest::ConvertToScopeEnum(ConvertScope(Input.Scope().value(), false)) == - Manifest::ConvertToScopeEnum(ConvertScope(Output.Scope().value(), false)); - } - else - { - return false; - } - } - else - { - return true; - } - } - bool TestLatest() { if (Input.UseLatest() && Input.UseLatest().value()) @@ -464,11 +406,6 @@ namespace AppInstaller::CLI { if (data.Output.Exist().value()) { - if (!data.TestScope()) - { - AICLI_LOG(CLI, Info, << "Reinstalling package to change scope"); - data.Reinstall(); - } if (!data.TestLatest()) { // Install will swap to update flow diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 7a44c8fdef..6c0e7119c2 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -25,8 +25,6 @@ public class DSCv3ResourceCommands 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 UserScope = "user"; - private const string SystemScope = "system"; private const string PackageResource = "package"; private const string GetFunction = "get"; private const string TestFunction = "test"; @@ -35,7 +33,6 @@ public class DSCv3ResourceCommands private const string ExistPropertyName = "_exist"; private const string VersionPropertyName = "version"; private const string UseLatestPropertyName = "useLatest"; - private const string ScopePropertyName = "scope"; /// /// Ensures that the test resources manifests are present. @@ -148,7 +145,6 @@ public void Package_Get_MuchInput() { Identifier = DefaultPackageIdentifier, Source = Constants.TestSourceName, - Scope = UserScope, MatchOption = "equals", }; @@ -302,56 +298,6 @@ public void Package_Test_NotLatest() AssertDiffState(diff, [ UseLatestPropertyName ]); } - /// - /// Calls `test` on the `package` resource with the scope that is present. - /// - [Test] - public void Package_Test_ScopeMatch() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Scope = UserScope, - }; - - 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 the scope that is not present. - /// - [Test] - public void Package_Test_ScopeMismatch() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Scope = SystemScope, - }; - - var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion); - Assert.False(output.InDesiredState); - - AssertDiffState(diff, [ ScopePropertyName ]); - } - /// /// Calls `set` on the `package` resource when the package is not present, and again afterward. /// @@ -421,42 +367,6 @@ public void Package_Set_Remove() Assert.AreEqual(packageResourceDataForGet.Identifier, output.Identifier); } - /// - /// Calls `set` on the `package` resource with a different scope from the one currently installed. - /// - [Test] - public void Package_Set_ChangeScope() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Scope = SystemScope, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion, SystemScope, ignoreLatest: true); - - AssertDiffState(diff, [ ScopePropertyName ]); - - // 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, SystemScope); - } - /// /// Calls `set` on the `package` resource to request the latest version when a lower version is installed. /// @@ -707,13 +617,12 @@ private static List GetOutputLinesAs(string output) return result; } - private static void AssertExistingPackageResourceData(PackageResourceData output, string version, string scope = UserScope, bool ignoreLatest = false) + 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); - Assert.AreEqual(scope, output.Scope.ToLower()); if (!ignoreLatest) { @@ -754,8 +663,6 @@ private class PackageResourceData public string Version { get; set; } - public string Scope { get; set; } - public string MatchOption { get; set; } public bool? UseLatest { get; set; } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 7f9b635704..65c787d8c3 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3284,6 +3284,6 @@ Please specify one of them using the --source option to proceed. Indicates whether to accept agreements for package installs. - An unused argument for logging + Value is logged for correlation \ No newline at end of file From 0a0d4baec8fdc511e6649e02257d85e853501003 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 22 Apr 2025 18:23:27 -0700 Subject: [PATCH 18/20] Fix for reinstall and set output not expecting useLatest; more logging for stuck test handling --- src/AppInstallerCLICore/Commands/DscPackageResource.cpp | 6 +++--- src/AppInstallerCLICore/ExecutionReporter.cpp | 1 + src/AppInstallerCLICore/Workflows/UpdateFlow.cpp | 2 +- src/AppInstallerCLICore/Workflows/UpdateFlow.h | 7 ++++--- src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs | 8 ++++---- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 1dc1ea2cd2..d3e480d3db 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -213,7 +213,7 @@ namespace AppInstaller::CLI Output.UseLatest(std::nullopt); } - void Install() + void Install(bool allowDowngrade = false) { AICLI_LOG(CLI, Verbose, << "Package::Install invoked"); @@ -223,7 +223,7 @@ namespace AppInstaller::CLI } *SubContext << - Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install) << + Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install, allowDowngrade) << Workflow::InstallSinglePackage; if (SubContext->IsTerminated()) @@ -253,7 +253,7 @@ namespace AppInstaller::CLI SubContext->Args.AddArg(Execution::Args::Type::UninstallPrevious); - Install(); + Install(true); } // Determines if the current Output values match the Input values state. diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 5de04d9acc..3d894b06e1 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -166,6 +166,7 @@ namespace AppInstaller::CLI::Execution bool Reporter::InputStreamIsInteractive() const { + AICLI_LOG(CLI, Verbose, << "Reporter::m_inStreamFileType is " << m_inStreamFileType); return m_inStreamFileType == FILE_TYPE_CHAR; } diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index dac0188c4e..a873e15b3b 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -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/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 6c0e7119c2..70932c6615 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -313,7 +313,7 @@ public void Package_Set_SimpleRepeated() AssertSuccessfulResourceRun(ref result); (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); AssertDiffState(diff, [ ExistPropertyName ]); @@ -386,7 +386,7 @@ public void Package_Set_Latest() AssertSuccessfulResourceRun(ref result); (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); AssertDiffState(diff, [ UseLatestPropertyName ]); @@ -422,7 +422,7 @@ public void Package_Set_SpecificVersionUpgrade() AssertSuccessfulResourceRun(ref result); (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); AssertDiffState(diff, [ VersionPropertyName ]); @@ -484,7 +484,7 @@ public void Package_Export_NoInput() var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); Assert.AreEqual(0, setupInstall.ExitCode); - var result = RunDSCv3Command(PackageResource, ExportFunction, null, 300000); + var result = RunDSCv3Command(PackageResource, ExportFunction, null); AssertSuccessfulResourceRun(ref result); List output = GetOutputLinesAs(result.StdOut); From a2c2284568cd6ce0b3537fd179c700d39ba2c05f Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 23 Apr 2025 11:25:56 -0700 Subject: [PATCH 19/20] Ignore latest more, given empty input rather than null so that the pipe is terminated --- src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 70932c6615..66494c2dae 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -458,7 +458,7 @@ public void Package_Set_SpecificVersionDowngrade() AssertSuccessfulResourceRun(ref result); (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); AssertDiffState(diff, [ VersionPropertyName ]); @@ -484,7 +484,7 @@ public void Package_Export_NoInput() var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); Assert.AreEqual(0, setupInstall.ExitCode); - var result = RunDSCv3Command(PackageResource, ExportFunction, null); + var result = RunDSCv3Command(PackageResource, ExportFunction, string.Empty); AssertSuccessfulResourceRun(ref result); List output = GetOutputLinesAs(result.StdOut); @@ -576,10 +576,12 @@ private static JsonSerializerOptions GetDefaultJsonOptions() }; } - private static string ConvertToJSON(object value) + private static string ConvertToJSON(object value) => value switch { - return value == null ? null : JsonSerializer.Serialize(value, GetDefaultJsonOptions()); - } + string s => s, + null => null, + _ => JsonSerializer.Serialize(value, GetDefaultJsonOptions()), + }; private static string[] GetOutputLines(string output) { From 116124d12e23f583db5e90d4007f3c38d0d782e6 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Wed, 23 Apr 2025 13:50:09 -0700 Subject: [PATCH 20/20] Non-empty required --- src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs index 66494c2dae..b8831c1709 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs @@ -484,7 +484,7 @@ public void Package_Export_NoInput() var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); Assert.AreEqual(0, setupInstall.ExitCode); - var result = RunDSCv3Command(PackageResource, ExportFunction, string.Empty); + var result = RunDSCv3Command(PackageResource, ExportFunction, " "); AssertSuccessfulResourceRun(ref result); List output = GetOutputLinesAs(result.StdOut);