diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
index 757853bbde..e0aa00db36 100644
--- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
+++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
@@ -306,6 +306,7 @@
+
@@ -392,6 +393,7 @@
+
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
index 0004d827b1..52b6f070e2 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/DscCommand.cpp b/src/AppInstallerCLICore/Commands/DscCommand.cpp
index d3a57b7b44..36dcba1c72 100644
--- a/src/AppInstallerCLICore/Commands/DscCommand.cpp
+++ b/src/AppInstallerCLICore/Commands/DscCommand.cpp
@@ -3,6 +3,7 @@
#include "pch.h"
#include "DscCommand.h"
#include "DscPackageResource.h"
+#include "DscSourceResource.h"
#ifndef AICLI_DISABLE_TEST_HOOKS
#include "DscTestFileResource.h"
@@ -15,6 +16,7 @@ namespace AppInstaller::CLI
{
return InitializeFromMoveOnly>>({
std::make_unique(FullName()),
+ 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 ec7f13faa0..3b3fa7ddc5 100644
--- a/src/AppInstallerCLICore/Commands/DscComposableObject.h
+++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h
@@ -101,13 +101,13 @@ namespace AppInstaller::CLI
//
// struct Property
// {
- // using Type = { bool, std::string };
+ // using PropertyType = { bool, std::string };
// static std::string_view Name();
// static void FromJson(Property*, const Json::Value*);
// static std::optional ToJson(const Property*);
//
- // const Type& PROPERTY_NAME() const;
- // void PROPERTY_NAME(const Type&);
+ // const PropertyType& PROPERTY_NAME() const;
+ // void PROPERTY_NAME(const PropertyType&);
// }
template
struct DscComposableObject : public Property...
@@ -149,15 +149,15 @@ 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::SchemaType(), Property::Description(), Property::EnumValues(), Property::Default()));
+ (FoldHelper{}, ..., details::AddPropertySchema(result, Property::Name(), Property::Flags, GetJsonTypeValue::SchemaType(), Property::Description(), Property::EnumValues(), Property::Default()));
return result;
}
};
- template
+ template
struct DscComposableProperty
{
- using Type = PropertyType;
+ using PropertyType = PropertyTypeT;
static constexpr DscComposablePropertyFlag Flags = PropertyFlags;
static void FromJson(Derived* self, const Json::Value* value, bool ignoreFieldRequirements)
@@ -201,7 +201,7 @@ namespace AppInstaller::CLI
}
protected:
- std::optional m_value;
+ std::optional m_value;
};
#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \
@@ -211,9 +211,9 @@ namespace AppInstaller::CLI
static Resource::LocString Description() { return _description_; } \
static std::vector EnumValues() { return std::vector _enum_vals_; } \
static std::optional Default() { return _default_; } \
- std::optional& _property_name_() { return m_value; } \
- const std::optional& _property_name_() const { return m_value; } \
- void _property_name_(std::optional value) { m_value = std::move(value); } \
+ 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_, _enum_vals_, _default_) \
WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \
diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp
index d3e480d3db..7df2afcff3 100644
--- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp
+++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp
@@ -24,7 +24,7 @@ namespace AppInstaller::CLI
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);
+ WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionAcceptAgreements);
// TODO: To support Scope on this resource:
// 1. Change the installed source to pull in all package info for both scopes by default
diff --git a/src/AppInstallerCLICore/Commands/DscSourceResource.cpp b/src/AppInstallerCLICore/Commands/DscSourceResource.cpp
new file mode 100644
index 0000000000..0623db11b2
--- /dev/null
+++ b/src/AppInstallerCLICore/Commands/DscSourceResource.cpp
@@ -0,0 +1,429 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+#include "pch.h"
+#include "DscSourceResource.h"
+#include "DscComposableObject.h"
+#include "Resources.h"
+#include "Workflows/SourceFlow.h"
+#include
+
+using namespace AppInstaller::Utility::literals;
+using namespace AppInstaller::Repository;
+
+namespace AppInstaller::CLI
+{
+ namespace
+ {
+ WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(NameProperty, std::string, SourceName, "name", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionSourceName);
+ WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ArgumentProperty, std::string, Argument, "argument", Resource::String::DscResourcePropertyDescriptionSourceArgument);
+ WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(TypeProperty, std::string, Type, "type", Resource::String::DscResourcePropertyDescriptionSourceType);
+ WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(TrustLevelProperty, std::string, TrustLevel, "trustLevel", Resource::String::DscResourcePropertyDescriptionSourceTrustLevel, ({ "undefined", "none", "trusted" }), "undefined");
+ WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ExplicitProperty, bool, Explicit, "explicit", Resource::String::DscResourcePropertyDescriptionSourceExplicit);
+ WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionAcceptAgreements);
+
+ using SourceResourceObject = DscComposableObject;
+
+ std::string TrustLevelStringFromFlags(SourceTrustLevel trustLevel)
+ {
+ return WI_IsFlagSet(trustLevel, SourceTrustLevel::Trusted) ? "trusted" : "none";
+ }
+
+ // The values as the resource uses them.
+ enum class ResourceTrustLevel
+ {
+ Undefined,
+ Invalid,
+ None,
+ Trusted
+ };
+
+ ResourceTrustLevel EffectiveTrustLevel(const std::optional& input)
+ {
+ if (!input)
+ {
+ return ResourceTrustLevel::Undefined;
+ }
+
+ std::string inputValue = Utility::ToLower(input.value());
+ if (inputValue == "undefined")
+ {
+ return ResourceTrustLevel::Undefined;
+ }
+ else if (inputValue == "none")
+ {
+ return ResourceTrustLevel::None;
+ }
+ else if (inputValue == "trusted")
+ {
+ return ResourceTrustLevel::Trusted;
+ }
+ else
+ {
+ return ResourceTrustLevel::Invalid;
+ }
+ }
+
+ struct SourceFunctionData
+ {
+ SourceFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) :
+ Input(json, ignoreFieldRequirements),
+ ParentContext(context)
+ {
+ Reset();
+ }
+
+ const SourceResourceObject Input;
+ SourceResourceObject Output;
+ Execution::Context& ParentContext;
+ std::unique_ptr SubContext;
+
+ // Reset the state that is modified by Get
+ void Reset()
+ {
+ Output = Input.CopyForOutput();
+
+ SubContext = ParentContext.CreateSubContext();
+ SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity);
+
+ if (Input.AcceptAgreements().value_or(false))
+ {
+ SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements);
+ }
+ }
+
+ // Fills the Output object with the current state
+ void Get()
+ {
+ auto currentSources = Repository::Source::GetCurrentSources();
+ const std::string& name = Input.SourceName().value();
+
+ Output.Exist(false);
+
+ for (auto const& source : currentSources)
+ {
+ if (Utility::ICUCaseInsensitiveEquals(source.Name, name))
+ {
+ Output.Exist(true);
+ Output.Argument(source.Arg);
+ Output.Type(source.Type);
+ Output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel));
+ Output.Explicit(source.Explicit);
+
+ std::vector sources;
+ sources.emplace_back(source);
+ SubContext->Add(std::move(sources));
+ break;
+ }
+ }
+
+ AICLI_LOG(CLI, Verbose, << "Source::Get found:\n" << Json::writeString(Json::StreamWriterBuilder{}, Output.ToJson()));
+ }
+
+ void Add()
+ {
+ AICLI_LOG(CLI, Verbose, << "Source::Add invoked");
+
+ if (!SubContext->Args.Contains(Execution::Args::Type::SourceName))
+ {
+ SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value());
+ }
+
+ THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !Input.Argument().has_value());
+ SubContext->Args.AddArg(Execution::Args::Type::SourceArg, Input.Argument().value());
+
+ if (Input.Type())
+ {
+ SubContext->Args.AddArg(Execution::Args::Type::SourceType, Input.Type().value());
+ }
+
+ ResourceTrustLevel effectiveTrustLevel = EffectiveTrustLevel(Input.TrustLevel());
+ THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, effectiveTrustLevel == ResourceTrustLevel::Invalid);
+ if (effectiveTrustLevel == ResourceTrustLevel::Trusted)
+ {
+ SubContext->Args.AddArg(Execution::Args::Type::SourceTrustLevel, TrustLevelStringFromFlags(SourceTrustLevel::Trusted));
+ }
+
+ if (Input.Explicit().value_or(false))
+ {
+ SubContext->Args.AddArg(Execution::Args::Type::SourceExplicit);
+ }
+
+ *SubContext <<
+ Workflow::EnsureRunningAsAdmin <<
+ Workflow::CreateSourceForSourceAdd <<
+ Workflow::AddSource;
+ }
+
+ void Remove()
+ {
+ AICLI_LOG(CLI, Verbose, << "Source::Remove invoked");
+
+ if (!SubContext->Args.Contains(Execution::Args::Type::SourceName))
+ {
+ SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value());
+ }
+
+ *SubContext <<
+ Workflow::EnsureRunningAsAdmin <<
+ Workflow::RemoveSources;
+ }
+
+ void Replace()
+ {
+ AICLI_LOG(CLI, Verbose, << "Source::Replace invoked");
+ Remove();
+ Add();
+ }
+
+ // Determines if the current Output values match the Input values state.
+ bool Test()
+ {
+ // Need to populate Output before calling
+ THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value());
+
+ if (Input.ShouldExist())
+ {
+ if (Output.Exist().value())
+ {
+ AICLI_LOG(CLI, Verbose, << "Source::Test needed to inspect these properties: Argument(" << TestArgument() << "), Type(" << TestType() << "), TrustLevel(" << TestTrustLevel() << "), Explicit(" << TestExplicit() << ")");
+ return TestArgument() && TestType() && TestTrustLevel() && TestExplicit();
+ }
+ else
+ {
+ AICLI_LOG(CLI, Verbose, << "Source::Test was false because the source is not present");
+ return false;
+ }
+ }
+ else
+ {
+ AICLI_LOG(CLI, Verbose, << "Source::Test desired the source to not exist, and it " << (Output.Exist().value() ? "did" : "did not"));
+ return !Output.Exist().value();
+ }
+ }
+
+ Json::Value DiffJson()
+ {
+ // Need to populate Output before calling
+ THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value());
+
+ Json::Value result{ Json::ValueType::arrayValue };
+
+ if (Input.ShouldExist() != Output.Exist().value())
+ {
+ result.append(std::string{ StandardExistProperty::Name() });
+ }
+ else
+ {
+ if (!TestArgument())
+ {
+ result.append(std::string{ ArgumentProperty::Name() });
+ }
+
+ if (!TestType())
+ {
+ result.append(std::string{ TypeProperty::Name() });
+ }
+
+ if (!TestTrustLevel())
+ {
+ result.append(std::string{ TrustLevelProperty::Name() });
+ }
+
+ if (!TestExplicit())
+ {
+ result.append(std::string{ ExplicitProperty::Name() });
+ }
+ }
+
+ return result;
+ }
+
+ bool TestArgument()
+ {
+ if (Input.Argument())
+ {
+ if (Output.Argument())
+ {
+ return Input.Argument().value() == Output.Argument().value();
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ bool TestType()
+ {
+ if (Input.Type())
+ {
+ if (Output.Type())
+ {
+ return Utility::CaseInsensitiveEquals(Input.Type().value(), Output.Type().value());
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ bool TestTrustLevel()
+ {
+ auto inputTrustLevel = EffectiveTrustLevel(Input.TrustLevel());
+
+ if (inputTrustLevel != ResourceTrustLevel::Undefined)
+ {
+ return inputTrustLevel == EffectiveTrustLevel(Output.TrustLevel());
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ bool TestExplicit()
+ {
+ if (Input.Explicit())
+ {
+ if (Output.Explicit())
+ {
+ return Input.Explicit().value() == Output.Explicit().value();
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return true;
+ }
+ }
+ };
+ }
+
+ DscSourceResource::DscSourceResource(std::string_view parent) :
+ DscCommandBase(parent, "source", DscResourceKind::Resource,
+ DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema,
+ DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff)
+ {
+ }
+
+ Resource::LocString DscSourceResource::ShortDescription() const
+ {
+ return Resource::String::DscSourceResourceShortDescription;
+ }
+
+ Resource::LocString DscSourceResource::LongDescription() const
+ {
+ return Resource::String::DscSourceResourceLongDescription;
+ }
+
+ std::string DscSourceResource::ResourceType() const
+ {
+ return "Source";
+ }
+
+ void DscSourceResource::ResourceFunctionGet(Execution::Context& context) const
+ {
+ if (auto json = GetJsonFromInput(context))
+ {
+ SourceFunctionData data{ context, json };
+
+ data.Get();
+
+ WriteJsonOutputLine(context, data.Output.ToJson());
+ }
+ }
+
+ void DscSourceResource::ResourceFunctionSet(Execution::Context& context) const
+ {
+ if (auto json = GetJsonFromInput(context))
+ {
+ SourceFunctionData data{ context, json };
+
+ data.Get();
+
+ // Capture the diff before updating the output
+ auto diff = data.DiffJson();
+
+ if (!data.Test())
+ {
+ if (data.Input.ShouldExist())
+ {
+ if (data.Output.Exist().value())
+ {
+ AICLI_LOG(CLI, Info, << "Replacing source with new information");
+ data.Replace();
+ }
+ else
+ {
+ AICLI_LOG(CLI, Info, << "Adding source as it was not found");
+ data.Add();
+ }
+ }
+ else
+ {
+ AICLI_LOG(CLI, Info, << "Removing source as desired");
+ data.Remove();
+ }
+
+ if (data.SubContext->IsTerminated())
+ {
+ data.ParentContext.Terminate(data.SubContext->GetTerminationHR());
+ return;
+ }
+
+ data.Reset();
+ data.Get();
+ }
+
+ WriteJsonOutputLine(context, data.Output.ToJson());
+ WriteJsonOutputLine(context, diff);
+ }
+ }
+
+ void DscSourceResource::ResourceFunctionTest(Execution::Context& context) const
+ {
+ if (auto json = GetJsonFromInput(context))
+ {
+ SourceFunctionData data{ context, json };
+
+ data.Get();
+ data.Output.InDesiredState(data.Test());
+
+ WriteJsonOutputLine(context, data.Output.ToJson());
+ WriteJsonOutputLine(context, data.DiffJson());
+ }
+ }
+
+ void DscSourceResource::ResourceFunctionExport(Execution::Context& context) const
+ {
+ auto currentSources = Repository::Source::GetCurrentSources();
+
+ for (auto const& source : currentSources)
+ {
+ SourceResourceObject output;
+ output.SourceName(source.Name);
+ output.Argument(source.Arg);
+ output.Type(source.Type);
+ output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel));
+ output.Explicit(source.Explicit);
+ WriteJsonOutputLine(context, output.ToJson());
+ }
+ }
+
+ void DscSourceResource::ResourceFunctionSchema(Execution::Context& context) const
+ {
+ WriteJsonOutputLine(context, SourceResourceObject::Schema(ResourceType()));
+ }
+}
diff --git a/src/AppInstallerCLICore/Commands/DscSourceResource.h b/src/AppInstallerCLICore/Commands/DscSourceResource.h
new file mode 100644
index 0000000000..236ef470d9
--- /dev/null
+++ b/src/AppInstallerCLICore/Commands/DscSourceResource.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 source configuration.
+ struct DscSourceResource : public DscCommandBase
+ {
+ DscSourceResource(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/Resources.h b/src/AppInstallerCLICore/Resources.h
index 8b885c45bb..ca93e577d3 100644
--- a/src/AppInstallerCLICore/Resources.h
+++ b/src/AppInstallerCLICore/Resources.h
@@ -228,6 +228,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionManifest);
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionExist);
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionInDesiredState);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAcceptAgreements);
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageId);
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageSource);
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageVersion);
@@ -235,7 +236,13 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageMatchOption);
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest);
WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode);
- WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageAcceptAgreements);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceName);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceArgument);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceType);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceTrustLevel);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceExplicit);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceShortDescription);
+ WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed);
WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess);
WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature);
diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h
index 069bd33ad9..e11cf29652 100644
--- a/src/AppInstallerCLICore/pch.h
+++ b/src/AppInstallerCLICore/pch.h
@@ -10,7 +10,7 @@
#include
#pragma warning( push )
-#pragma warning ( disable : 4458 4100 6031 4702 )
+#pragma warning ( disable : 4458 4100 6031 4702 26439 )
#include
#include
#include
diff --git a/src/AppInstallerCLIE2ETests/ConfigureCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureCommand.cs
index 0abb6f400f..abb88d3b92 100644
--- a/src/AppInstallerCLIE2ETests/ConfigureCommand.cs
+++ b/src/AppInstallerCLIE2ETests/ConfigureCommand.cs
@@ -8,9 +8,7 @@ namespace AppInstallerCLIE2ETests
{
using System;
using System.IO;
- using System.Linq;
using AppInstallerCLIE2ETests.Helpers;
- using Microsoft.Win32;
using NUnit.Framework;
///
@@ -25,14 +23,8 @@ public class ConfigureCommand
///
public static void EnsureTestResourcePresence()
{
- string outputDirectory = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\WindowsApps");
- Assert.IsNotEmpty(outputDirectory);
-
- var result = TestCommon.RunAICLICommand("dscv3 test-file", $"--manifest -o {outputDirectory}\\test-file.dsc.resource.json");
- Assert.AreEqual(0, result.ExitCode);
-
- result = TestCommon.RunAICLICommand("dscv3 test-json", $"--manifest -o {outputDirectory}\\test-json.dsc.resource.json");
- Assert.AreEqual(0, result.ExitCode);
+ DSCv3ResourceTestBase.EnsureTestResourcePresence("test-file");
+ DSCv3ResourceTestBase.EnsureTestResourcePresence("test-json");
}
///
diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs b/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs
similarity index 83%
rename from src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs
rename to src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs
index b8831c1709..b01ecc6352 100644
--- a/src/AppInstallerCLIE2ETests/DSCv3ResourceCommands.cs
+++ b/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs
@@ -1,15 +1,12 @@
// -----------------------------------------------------------------------------
-//
+//
// 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;
@@ -19,33 +16,16 @@ 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 DSCv3PackageResourceCommand : DSCv3ResourceTestBase
{
private const string DefaultPackageIdentifier = Constants.ExeInstallerPackageId;
private const string DefaultPackageLowVersion = "1.0.0.0";
private const string DefaultPackageMidVersion = "1.1.0.0";
private const string DefaultPackageHighVersion = "2.0.0.0";
private const string PackageResource = "package";
- private const string GetFunction = "get";
- private const string TestFunction = "test";
- private const string SetFunction = "set";
- private const string ExportFunction = "export";
- private const string ExistPropertyName = "_exist";
private const string VersionPropertyName = "version";
private const string UseLatestPropertyName = "useLatest";
- ///
- /// Ensures that the test resources manifests are present.
- ///
- public static void EnsureTestResourcePresence()
- {
- string outputDirectory = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\WindowsApps");
- Assert.IsNotEmpty(outputDirectory);
-
- var result = TestCommon.RunAICLICommand("dscv3 package", $"--manifest -o {outputDirectory}\\microsoft.winget.package.dsc.resource.json");
- Assert.AreEqual(0, result.ExitCode);
- }
-
///
/// Setup done once before all the tests here.
///
@@ -55,7 +35,7 @@ public void OneTimeSetup()
TestCommon.SetupTestSource();
WinGetSettingsHelper.ConfigureFeature("dsc3", true);
WinGetSettingsHelper.ConfigureLoggingLevel("verbose");
- EnsureTestResourcePresence();
+ EnsureTestResourcePresence(PackageResource);
}
///
@@ -172,9 +152,7 @@ public void Package_Test_NotPresent()
Assert.AreEqual(packageResourceData.Identifier, output.Identifier);
Assert.False(output.InDesiredState);
- Assert.IsNotNull(diff);
- Assert.AreEqual(1, diff.Count);
- Assert.AreEqual(ExistPropertyName, diff[0]);
+ AssertDiffState(diff, [ ExistPropertyName ]);
}
///
@@ -552,73 +530,6 @@ private static void RemoveTestPackage()
AssertSuccessfulResourceRun(ref result);
}
- private static TestCommon.RunCommandResult RunDSCv3Command(string resource, string function, object input, int timeOut = 60000, bool throwOnTimeout = true)
- {
- return TestCommon.RunAICLICommand($"dscv3 {resource}", $"--{function}", ConvertToJSON(input), timeOut, throwOnTimeout);
- }
-
- private static void AssertSuccessfulResourceRun(ref TestCommon.RunCommandResult result)
- {
- Assert.AreEqual(0, result.ExitCode);
- Assert.IsNotEmpty(result.StdOut);
- }
-
- private static JsonSerializerOptions GetDefaultJsonOptions()
- {
- return new JsonSerializerOptions()
- {
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- Converters =
- {
- new JsonStringEnumConverter(),
- },
- };
- }
-
- private static string ConvertToJSON(object value) => value switch
- {
- string s => s,
- null => null,
- _ => JsonSerializer.Serialize(value, GetDefaultJsonOptions()),
- };
-
- private static string[] GetOutputLines(string output)
- {
- return output.TrimEnd().Split(Environment.NewLine);
- }
-
- private static T GetSingleOutputLineAs(string output)
- {
- string[] lines = GetOutputLines(output);
- Assert.AreEqual(1, lines.Length);
-
- return JsonSerializer.Deserialize(lines[0], GetDefaultJsonOptions());
- }
-
- private static (T, List) GetSingleOutputLineAndDiffAs(string output)
- {
- string[] lines = GetOutputLines(output);
- Assert.AreEqual(2, lines.Length);
-
- var options = GetDefaultJsonOptions();
- return (JsonSerializer.Deserialize(lines[0], options), JsonSerializer.Deserialize>(lines[1], options));
- }
-
- private static List GetOutputLinesAs(string output)
- {
- List result = new List();
- string[] lines = GetOutputLines(output);
- var options = GetDefaultJsonOptions();
-
- foreach (string line in lines)
- {
- result.Add(JsonSerializer.Deserialize(line, options));
- }
-
- return result;
- }
-
private static void AssertExistingPackageResourceData(PackageResourceData output, string version, bool ignoreLatest = false)
{
Assert.IsNotNull(output);
@@ -639,23 +550,12 @@ private static void AssertExistingPackageResourceData(PackageResourceData output
}
}
- 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")]
+ [JsonPropertyName(InDesiredStatePropertyName)]
public bool? InDesiredState { get; set; }
[JsonPropertyName("id")]
diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs
new file mode 100644
index 0000000000..838eb17375
--- /dev/null
+++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs
@@ -0,0 +1,184 @@
+// -----------------------------------------------------------------------------
+//
+// 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;
+
+ ///
+ /// Provides common functionality for DSC v3 resource tests.
+ ///
+ public class DSCv3ResourceTestBase
+ {
+ ///
+ /// The string for the `get` function.
+ ///
+ public const string GetFunction = "get";
+
+ ///
+ /// The string for the `test` function.
+ ///
+ public const string TestFunction = "test";
+
+ ///
+ /// The string for the `set` function.
+ ///
+ public const string SetFunction = "set";
+
+ ///
+ /// The string for the `export` function.
+ ///
+ public const string ExportFunction = "export";
+
+ ///
+ /// The string for the `_exist` property name.
+ ///
+ public const string ExistPropertyName = "_exist";
+
+ ///
+ /// The string for the `_inDesiredState` property name.
+ ///
+ public const string InDesiredStatePropertyName = "_inDesiredState";
+
+ ///
+ /// Write the resource manifest out to the WindowsApps alias directory.
+ ///
+ /// The resource manifest to write.
+ public static void EnsureTestResourcePresence(string resource)
+ {
+ string outputDirectory = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\WindowsApps");
+ Assert.IsNotEmpty(outputDirectory);
+
+ var result = TestCommon.RunAICLICommand($"dscv3 {resource}", $"--manifest -o {outputDirectory}\\microsoft.winget.{resource}.dsc.resource.json");
+ Assert.AreEqual(0, result.ExitCode);
+ }
+
+ ///
+ /// Runs a DSC v3 resource command.
+ ///
+ /// The resource to target.
+ /// The resource function to run.
+ /// Input for the function; supports null, direct string, or JSON serialization of complex objects.
+ /// The maximum time to wait in milliseconds.
+ /// Whether to throw on a timeout or simply return the incomplete result.
+ /// A RunCommandResult containing the process exit code and output and error streams.
+ protected 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);
+ }
+
+ ///
+ /// Asserts that a RunCommandResult contains a success for a DSC v3 resource command run.
+ ///
+ /// The result of a DSC v3 resource command run.
+ protected static void AssertSuccessfulResourceRun(ref TestCommon.RunCommandResult result)
+ {
+ Assert.AreEqual(0, result.ExitCode);
+ Assert.IsNotEmpty(result.StdOut);
+ }
+
+ ///
+ /// Gets the output as lines.
+ ///
+ /// The output stream from a DSC v3 resource command.
+ /// The lines of the output.
+ protected static string[] GetOutputLines(string output)
+ {
+ return output.TrimEnd().Split(Environment.NewLine);
+ }
+
+ ///
+ /// Asserts that the output is a single line and deserializes that line as JSON.
+ ///
+ /// The type to deserialize from JSON.
+ /// The output stream from a DSC v3 resource command.
+ /// The object as deserialized.
+ protected static T GetSingleOutputLineAs(string output)
+ {
+ string[] lines = GetOutputLines(output);
+ Assert.AreEqual(1, lines.Length);
+
+ return JsonSerializer.Deserialize(lines[0], GetDefaultJsonOptions());
+ }
+
+ ///
+ /// Asserts that the output is two lines and deserializes them as a JSON object and JSON string array.
+ ///
+ /// The type to deserialize from JSON.
+ /// The output stream from a DSC v3 resource command.
+ /// The object as deserialized and the contents of the string array.
+ protected 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));
+ }
+
+ ///
+ /// Deserializes all lines as JSON objects.
+ ///
+ /// The type to deserialize from JSON.
+ /// The output stream from a DSC v3 resource command.
+ /// A List of objects as deserialized.
+ protected 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;
+ }
+
+ ///
+ /// Requires that the diff from a resource command contain the same set of strings as expected.
+ ///
+ /// The diff from a resource command.
+ /// The expected strings.
+ protected 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 static JsonSerializerOptions GetDefaultJsonOptions()
+ {
+ return new JsonSerializerOptions()
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters =
+ {
+ new JsonStringEnumConverter(),
+ },
+ };
+ }
+
+ private static string ConvertToJSON(object value) => value switch
+ {
+ string s => s,
+ null => null,
+ _ => JsonSerializer.Serialize(value, GetDefaultJsonOptions()),
+ };
+ }
+}
diff --git a/src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs b/src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs
new file mode 100644
index 0000000000..9a27c7ad42
--- /dev/null
+++ b/src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs
@@ -0,0 +1,485 @@
+// -----------------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
+//
+// -----------------------------------------------------------------------------
+
+namespace AppInstallerCLIE2ETests
+{
+ using System.Collections.Generic;
+ using System.Text.Json.Serialization;
+ using AppInstallerCLIE2ETests.Helpers;
+ using NUnit.Framework;
+ using WinRT;
+
+ ///
+ /// `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 DSCv3SourceResourceCommand : DSCv3ResourceTestBase
+ {
+ private const string DefaultSourceName = "SourceResourceTestSource";
+ private const string DefaultSourceType = "Microsoft.Test.Configurable";
+ private const string DefaultTrustLevel = "none";
+ private const string TrustedTrustLevel = "trusted";
+ private const bool DefaultExplicitState = false;
+ private const string SourceResource = "source";
+ private const string ArgumentPropertyName = "argument";
+ private const string TypePropertyName = "type";
+ private const string TrustLevelPropertyName = "trustLevel";
+ private const string ExplicitPropertyName = "explicit";
+
+ private static string DefaultSourceArgForCmdLine
+ {
+ get { return CreateSourceArgument(true); }
+ }
+
+ private static string NonDefaultSourceArgForCmdLine
+ {
+ get { return CreateSourceArgument(true, 1, 1); }
+ }
+
+ private static string DefaultSourceArgDirect
+ {
+ get { return CreateSourceArgument(false); }
+ }
+
+ private static string NonDefaultSourceArgDirect
+ {
+ get { return CreateSourceArgument(false, 1, 1); }
+ }
+
+ ///
+ /// Setup done once before all the tests here.
+ ///
+ [OneTimeSetUp]
+ public void OneTimeSetup()
+ {
+ TestCommon.SetupTestSource();
+ WinGetSettingsHelper.ConfigureFeature("dsc3", true);
+ EnsureTestResourcePresence(SourceResource);
+ }
+
+ ///
+ /// Teardown done once after all the tests here.
+ ///
+ [OneTimeTearDown]
+ public void OneTimeTeardown()
+ {
+ RemoveTestSource();
+ WinGetSettingsHelper.ConfigureFeature("dsc3", false);
+ TestCommon.TearDownTestSource();
+ }
+
+ ///
+ /// Set up.
+ ///
+ [SetUp]
+ public void Setup()
+ {
+ RemoveTestSource();
+ }
+
+ ///
+ /// Calls `get` on the `source` resource with the value not present.
+ ///
+ [Test]
+ public void Source_Get_NotPresent()
+ {
+ SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName };
+
+ var result = RunDSCv3Command(SourceResource, GetFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ SourceResourceData output = GetSingleOutputLineAs(result.StdOut);
+ Assert.IsNotNull(output);
+ Assert.False(output.Exist);
+ Assert.AreEqual(resourceData.Name, output.Name);
+ }
+
+ ///
+ /// Calls `get` on the `source` resource with the value present.
+ ///
+ [Test]
+ public void Source_Get_Present()
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType} --explicit");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName };
+
+ var result = RunDSCv3Command(SourceResource, GetFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ SourceResourceData output = GetSingleOutputLineAs(result.StdOut);
+ AssertExistingSourceResourceData(output, DefaultSourceArgDirect, DefaultTrustLevel, true);
+ }
+
+ ///
+ /// Calls `test` on the `source` resource with the value not present.
+ ///
+ [Test]
+ public void Source_Test_NotPresent()
+ {
+ SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName };
+
+ var result = RunDSCv3Command(SourceResource, TestFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ Assert.IsNotNull(output);
+ Assert.False(output.Exist);
+ Assert.AreEqual(resourceData.Name, output.Name);
+ Assert.False(output.InDesiredState);
+
+ AssertDiffState(diff, [ ExistPropertyName ]);
+ }
+
+ ///
+ /// Calls `test` on the `source` resource with the value present.
+ ///
+ [Test]
+ public void Source_Test_SimplePresent()
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName };
+
+ var result = RunDSCv3Command(SourceResource, TestFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ AssertExistingSourceResourceData(output, DefaultSourceArgDirect, DefaultTrustLevel, DefaultExplicitState);
+ Assert.True(output.InDesiredState);
+
+ AssertDiffState(diff, []);
+ }
+
+ ///
+ /// Calls `test` on the `source` resource with a argument that matches.
+ ///
+ /// The argument to use when adding the existing source.
+ /// The trust level to use when adding the existing source.
+ /// The explicit state to use when adding the existing source.
+ /// The property to target for the test.
+ [TestCase(false, DefaultTrustLevel, true, ArgumentPropertyName)]
+ [TestCase(true, DefaultTrustLevel, false, TypePropertyName)]
+ [TestCase(false, TrustedTrustLevel, false, TrustLevelPropertyName)]
+ [TestCase(true, DefaultTrustLevel, true, ExplicitPropertyName)]
+ public void Source_Test_PropertyMatch(bool useDefaultArgument, string trustLevel, bool isExplicit, string targetProperty)
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {(useDefaultArgument ? DefaultSourceArgForCmdLine : NonDefaultSourceArgForCmdLine)} --type {DefaultSourceType} --trust-level {trustLevel} {(isExplicit ? "--explicit" : string.Empty)}");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName };
+
+ switch (targetProperty)
+ {
+ case ArgumentPropertyName:
+ resourceData.Argument = useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect;
+ break;
+ case TypePropertyName:
+ resourceData.Type = DefaultSourceType;
+ break;
+ case TrustLevelPropertyName:
+ resourceData.TrustLevel = trustLevel;
+ break;
+ case ExplicitPropertyName:
+ resourceData.Explicit = isExplicit;
+ break;
+ default:
+ Assert.Fail($"{targetProperty} is not a handled case.");
+ break;
+ }
+
+ var result = RunDSCv3Command(SourceResource, TestFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ AssertExistingSourceResourceData(output, useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect, trustLevel, isExplicit);
+ Assert.True(output.InDesiredState);
+
+ AssertDiffState(diff, []);
+ }
+
+ ///
+ /// Calls `test` on the `source` resource with a argument that does not match.
+ ///
+ /// The argument to use when adding the existing source.
+ /// The trust level to use when adding the existing source.
+ /// The explicit state to use when adding the existing source.
+ /// The property to target for the test.
+ /// The value to test against.
+ [TestCase(false, DefaultTrustLevel, true, ArgumentPropertyName, true)]
+ [TestCase(false, DefaultTrustLevel, false, TrustLevelPropertyName, TrustedTrustLevel)]
+ [TestCase(true, DefaultTrustLevel, true, ExplicitPropertyName, false)]
+ public void Source_Test_PropertyMismatch(bool useDefaultArgument, string trustLevel, bool isExplicit, string targetProperty, object testValue)
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {(useDefaultArgument ? DefaultSourceArgForCmdLine : NonDefaultSourceArgForCmdLine)} --type {DefaultSourceType} --trust-level {trustLevel} {(isExplicit ? "--explicit" : string.Empty)}");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName };
+
+ switch (targetProperty)
+ {
+ case ArgumentPropertyName:
+ resourceData.Argument = (bool)testValue ? DefaultSourceArgDirect : NonDefaultSourceArgDirect;
+ break;
+ case TrustLevelPropertyName:
+ resourceData.TrustLevel = (string)testValue;
+ break;
+ case ExplicitPropertyName:
+ resourceData.Explicit = (bool)testValue;
+ break;
+ default:
+ Assert.Fail($"{targetProperty} is not a handled case.");
+ break;
+ }
+
+ var result = RunDSCv3Command(SourceResource, TestFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ AssertExistingSourceResourceData(output, useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect, trustLevel, isExplicit);
+ Assert.False(output.InDesiredState);
+
+ AssertDiffState(diff, [ targetProperty ]);
+ }
+
+ ///
+ /// Calls `test` on the `source` resource with all properties matching.
+ ///
+ [Test]
+ public void Source_Test_AllMatch()
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {NonDefaultSourceArgForCmdLine} --type {DefaultSourceType} --trust-level {TrustedTrustLevel} --explicit");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ SourceResourceData resourceData = new SourceResourceData()
+ {
+ Name = DefaultSourceName,
+ Argument = NonDefaultSourceArgDirect,
+ Type = DefaultSourceType,
+ TrustLevel = TrustedTrustLevel,
+ Explicit = true,
+ };
+
+ var result = RunDSCv3Command(SourceResource, TestFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ AssertExistingSourceResourceData(output, resourceData);
+ Assert.True(output.InDesiredState);
+
+ AssertDiffState(diff, []);
+ }
+
+ ///
+ /// Calls `set` on the `source` resource when the source is not present, and again afterward.
+ ///
+ [Test]
+ public void Source_Set_SimpleRepeated()
+ {
+ SourceResourceData resourceData = new SourceResourceData()
+ {
+ Name = DefaultSourceName,
+ Argument = NonDefaultSourceArgDirect,
+ Type = DefaultSourceType,
+ };
+
+ var result = RunDSCv3Command(SourceResource, SetFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ AssertExistingSourceResourceData(output, resourceData);
+
+ AssertDiffState(diff, [ ExistPropertyName ]);
+
+ // Set again should be a no-op
+ result = RunDSCv3Command(SourceResource, SetFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (output, diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ AssertExistingSourceResourceData(output, resourceData);
+
+ AssertDiffState(diff, []);
+ }
+
+ ///
+ /// Calls `set` on the `source` resource to ensure that it is not present.
+ ///
+ [Test]
+ public void Source_Set_Remove()
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType} --explicit");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ SourceResourceData resourceData = new SourceResourceData()
+ {
+ Name = DefaultSourceName,
+ Exist = false,
+ };
+
+ var result = RunDSCv3Command(SourceResource, SetFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ Assert.IsNotNull(output);
+ Assert.False(output.Exist);
+ Assert.AreEqual(resourceData.Name, output.Name);
+
+ AssertDiffState(diff, [ ExistPropertyName ]);
+
+ // Call `get` to ensure the result
+ SourceResourceData resourceDataForGet = new SourceResourceData()
+ {
+ Name = DefaultSourceName,
+ };
+
+ result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet);
+ AssertSuccessfulResourceRun(ref result);
+
+ output = GetSingleOutputLineAs(result.StdOut);
+ Assert.IsNotNull(output);
+ Assert.False(output.Exist);
+ Assert.AreEqual(resourceDataForGet.Name, output.Name);
+ }
+
+ ///
+ /// Calls `set` on the `source` resource with an existing item, replacing it.
+ ///
+ [Test]
+ public void Source_Set_Replace()
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ SourceResourceData resourceData = new SourceResourceData()
+ {
+ Name = DefaultSourceName,
+ Argument = DefaultSourceArgDirect,
+ Type = DefaultSourceType,
+ TrustLevel = TrustedTrustLevel,
+ Explicit = true,
+ };
+
+ var result = RunDSCv3Command(SourceResource, SetFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+
+ (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut);
+ AssertExistingSourceResourceData(output, resourceData);
+
+ AssertDiffState(diff, [ TrustLevelPropertyName, ExplicitPropertyName ]);
+
+ // Call `get` to ensure the result
+ SourceResourceData resourceDataForGet = new SourceResourceData()
+ {
+ Name = DefaultSourceName,
+ };
+
+ result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet);
+ AssertSuccessfulResourceRun(ref result);
+
+ output = GetSingleOutputLineAs(result.StdOut);
+ AssertExistingSourceResourceData(output, resourceData);
+ }
+
+ ///
+ /// Calls `export` on the `source` resource without providing any input.
+ ///
+ [Test]
+ public void Source_Export_NoInput()
+ {
+ var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}");
+ Assert.AreEqual(0, setup.ExitCode);
+
+ var result = RunDSCv3Command(SourceResource, ExportFunction, " ");
+ AssertSuccessfulResourceRun(ref result);
+
+ List output = GetOutputLinesAs(result.StdOut);
+
+ bool foundDefaultSource = false;
+ foreach (SourceResourceData item in output)
+ {
+ if (item.Name == DefaultSourceName)
+ {
+ foundDefaultSource = true;
+ Assert.AreEqual(DefaultSourceName, item.Name);
+ Assert.AreEqual(DefaultSourceArgDirect, item.Argument);
+ Assert.AreEqual(DefaultSourceType, item.Type);
+ Assert.AreEqual(DefaultTrustLevel, item.TrustLevel);
+ Assert.AreEqual(DefaultExplicitState, item.Explicit);
+ break;
+ }
+ }
+
+ Assert.IsTrue(foundDefaultSource);
+ }
+
+ private static void RemoveTestSource()
+ {
+ SourceResourceData resourceData = new SourceResourceData()
+ {
+ Name = DefaultSourceName,
+ Exist = false,
+ };
+
+ var result = RunDSCv3Command(SourceResource, SetFunction, resourceData);
+ AssertSuccessfulResourceRun(ref result);
+ }
+
+ private static void AssertExistingSourceResourceData(SourceResourceData output, SourceResourceData input)
+ {
+ AssertExistingSourceResourceData(output, input.Argument, input.TrustLevel, input.Explicit);
+ }
+
+ private static void AssertExistingSourceResourceData(SourceResourceData output, string argument, string trustLevel = null, bool? isExplicit = null)
+ {
+ Assert.IsNotNull(output);
+ Assert.True(output.Exist);
+ Assert.AreEqual(DefaultSourceName, output.Name);
+ Assert.AreEqual(argument, output.Argument);
+ Assert.AreEqual(DefaultSourceType, output.Type);
+
+ if (trustLevel != null)
+ {
+ Assert.AreEqual(trustLevel, output.TrustLevel);
+ }
+
+ if (isExplicit != null)
+ {
+ Assert.AreEqual(isExplicit, output.Explicit);
+ }
+ }
+
+ private static string CreateSourceArgument(bool forCommandLine = false, int openHR = 0, int searchHR = 0)
+ {
+ const string CommandLineFormat = @"""{{""""OpenHR"""": {0}, """"SearchHR"""": {1} }}""";
+ const string DirectFormat = @"{{""OpenHR"": {0}, ""SearchHR"": {1} }}";
+ return string.Format(forCommandLine ? CommandLineFormat : DirectFormat, openHR, searchHR);
+ }
+
+ private class SourceResourceData
+ {
+ [JsonPropertyName(ExistPropertyName)]
+ public bool? Exist { get; set; }
+
+ [JsonPropertyName(InDesiredStatePropertyName)]
+ public bool? InDesiredState { get; set; }
+
+ public string Name { get; set; }
+
+ public string Argument { get; set; }
+
+ public string Type { get; set; }
+
+ public string TrustLevel { get; set; }
+
+ public bool? Explicit { get; set; }
+
+ public bool? AcceptAgreements { get; set; }
+ }
+ }
+}
diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
index 65c787d8c3..1f7f69c2c9 100644
--- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
+++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
@@ -3280,10 +3280,31 @@ Please specify one of them using the --source option to proceed.
The target scope of the package.
-
- Indicates whether to accept agreements for package installs.
+
+ Indicates whether to accept agreements for sources and packages.
Value is logged for correlation
+
+ Manage source configuration
+
+
+ Allows management of source configuration via the DSC v3 command line interface protocol. See the help link for details.
+
+
+ The name to use for the source.
+
+
+ The argument for the source.
+
+
+ The type of the source.
+
+
+ The trust level of the source.
+
+
+ Whether the source is included when calls don't specify a source.
+
\ No newline at end of file
diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h
index 2825b5ff5c..bf28a8c0c6 100644
--- a/src/AppInstallerCommonCore/pch.h
+++ b/src/AppInstallerCommonCore/pch.h
@@ -28,7 +28,7 @@
#include
#pragma warning( push )
-#pragma warning ( disable : 4458 4100 4702 6031 )
+#pragma warning ( disable : 4458 4100 4702 6031 26439 )
#include
#include
#include
diff --git a/src/AppInstallerSharedLib/pch.h b/src/AppInstallerSharedLib/pch.h
index d9bf1dd1ec..57f5054501 100644
--- a/src/AppInstallerSharedLib/pch.h
+++ b/src/AppInstallerSharedLib/pch.h
@@ -19,7 +19,7 @@
#include
#pragma warning( push )
-#pragma warning ( disable : 4458 4100 4702 6031 )
+#pragma warning ( disable : 4458 4100 4702 6031 26439 )
#include
#include
#include