commit 511d4f9815af8070b4d0d80fe9fbd2024f5f96e3 parent bf264aa4225e577070b72c1ebee8e75b5a517ec1 Author: JohnMcPMS <johnmcp@microsoft.com> Date: Mon, 31 Mar 2025 10:41:32 -0700 DSC v3 Export (#5319) ## Change Adds a more generic `GetAllUnits(Async)` export function to the `ConfigurationProcessor`. This produces full configuration units, which may then differ from the given unit's type. This models the DSC v3 `Exporter` resource type. The implementation of this function prefers the full implementation from the processor, but if not provided it can fall back to the previous `GetAllSettings` function and synthesize results. Implements both the "all settings" and "all units" functionality for the DSC v3 processor. This in turn is done through the only DSC v3 option, `export`. Diffstat:
42 files changed, 1336 insertions(+), 37 deletions(-)
diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -306,6 +306,7 @@ <ClInclude Include="Commands\DscCommandBase.h" /> <ClInclude Include="Commands\DscComposableObject.h" /> <ClInclude Include="Commands\DscTestFileResource.h" /> + <ClInclude Include="Commands\DscTestJsonResource.h" /> <ClInclude Include="Commands\ErrorCommand.h" /> <ClInclude Include="Commands\ExperimentalCommand.h" /> <ClInclude Include="Commands\ExportCommand.h" /> @@ -391,6 +392,7 @@ <ClCompile Include="Commands\DscCommandBase.cpp" /> <ClCompile Include="Commands\DscComposableObject.cpp" /> <ClCompile Include="Commands\DscTestFileResource.cpp" /> + <ClCompile Include="Commands\DscTestJsonResource.cpp" /> <ClCompile Include="Commands\ErrorCommand.cpp" /> <ClCompile Include="Commands\FontCommand.cpp" /> <ClCompile Include="Commands\ImportCommand.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -281,6 +281,9 @@ <ClInclude Include="Commands\DscComposableObject.h"> <Filter>Commands\Configuration</Filter> </ClInclude> + <ClInclude Include="Commands\DscTestJsonResource.h"> + <Filter>Commands\Configuration</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -529,6 +532,9 @@ <ClCompile Include="Commands\DscComposableObject.cpp"> <Filter>Commands\Configuration</Filter> </ClCompile> + <ClCompile Include="Commands\DscTestJsonResource.cpp"> + <Filter>Commands\Configuration</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Commands/DscCommand.cpp b/src/AppInstallerCLICore/Commands/DscCommand.cpp @@ -5,6 +5,7 @@ #ifndef AICLI_DISABLE_TEST_HOOKS #include "DscTestFileResource.h" +#include "DscTestJsonResource.h" #endif namespace AppInstaller::CLI @@ -14,6 +15,7 @@ namespace AppInstaller::CLI return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({ #ifndef AICLI_DISABLE_TEST_HOOKS std::make_unique<DscTestFileResource>(FullName()), + std::make_unique<DscTestJsonResource>(FullName()), #endif }); } diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp @@ -92,6 +92,21 @@ namespace AppInstaller::CLI return false; } + std::optional<std::string> GetReturnType(DscFunctionModifiers modifiers) + { + if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsStateAndDiff)) + { + return "stateAndDiff"; + } + + if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsState)) + { + return "state"; + } + + return std::nullopt; + } + Json::Value CreateJsonDefinitionFor(std::string_view name, DscFunctions function, DscFunctionModifiers modifiers) { THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function)); @@ -131,7 +146,12 @@ namespace AppInstaller::CLI if (FunctionSpecifiesReturn(function)) { - result["return"] = "stateAndDiff"; + std::optional<std::string> returnType = GetReturnType(modifiers); + + if (returnType) + { + result["return"] = returnType.value(); + } } if (function == DscFunctions::Schema) diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.h b/src/AppInstallerCLICore/Commands/DscCommandBase.h @@ -58,6 +58,10 @@ namespace AppInstaller::CLI // The resource will act on the `_exist` property during Set (and WhatIf). // If not provided, the resource should implement Delete. HandlesExist = 0x02, + // Functions that may return state information (set, what-if, test) return only the state. + ReturnsState = 0x04, + // Functions that may return state information (set, what-if, test) return the state and property difference. + ReturnsStateAndDiff = 0x08, }; DEFINE_ENUM_FLAG_OPERATORS(DscFunctionModifiers); diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.cpp b/src/AppInstallerCLICore/Commands/DscComposableObject.cpp @@ -45,7 +45,11 @@ namespace AppInstaller::CLI Json::Value property{ Json::ValueType::objectValue }; - property["type"] = std::string{ type }; + if (!type.empty()) + { + property["type"] = std::string{ type }; + } + property["description"] = std::string{ description }; propertiesObject[nameString] = std::move(property); diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -3,6 +3,7 @@ #pragma once #include <AppInstallerErrors.h> #include <AppInstallerLanguageUtilities.h> +#include <AppInstallerLogging.h> #include <json/json.h> #include <optional> @@ -69,6 +70,21 @@ namespace AppInstaller::CLI } }; + template <> + struct GetJsonTypeValue<Json::Value> + { + static Json::Value Get(const Json::Value& value) + { + return value; + } + + static std::string_view SchemaTypeName() + { + // Indicates that the schema should not set a type + return {}; + } + }; + // Template useful for composing objects for DSC resources. // Properties should be of the shape: // @@ -140,7 +156,7 @@ namespace AppInstaller::CLI { if constexpr (WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::Required)) { - THROW_HR(WINGET_CONFIG_ERROR_MISSING_FIELD); + THROW_HR_MSG(WINGET_CONFIG_ERROR_MISSING_FIELD, "Required property `%hs` not provided.", Derived::Name().data()); } else { diff --git a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp @@ -15,9 +15,9 @@ namespace AppInstaller::CLI using TestFileObject = DscComposableObject<StandardExistProperty, StandardInDesiredStateProperty, PathProperty, ContentProperty>; - struct FunctionData + struct TestFileFunctionData { - FunctionData(const std::optional<Json::Value>& json) : Input(json), Output(Input.CopyForOutput()) + TestFileFunctionData(const std::optional<Json::Value>& json) : Input(json), Output(Input.CopyForOutput()) { Path = Utility::ConvertToUTF16(Input.Path().value()); THROW_HR_IF(E_INVALIDARG, !Path.is_absolute()); @@ -104,7 +104,7 @@ namespace AppInstaller::CLI DscTestFileResource::DscTestFileResource(std::string_view parent) : DscCommandBase(parent, "test-file", DscResourceKind::Resource, DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, - DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist) + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) { } @@ -127,7 +127,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::FunctionData data{ json }; + anon::TestFileFunctionData data{ json }; data.Get(); @@ -139,7 +139,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::FunctionData data{ json }; + anon::TestFileFunctionData data{ json }; data.Get(); @@ -186,7 +186,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::FunctionData data{ json }; + anon::TestFileFunctionData data{ json }; data.Get(); data.Output.InDesiredState(data.Test()); @@ -200,7 +200,7 @@ namespace AppInstaller::CLI { if (auto json = GetJsonFromInput(context)) { - anon::FunctionData data{ json }; + anon::TestFileFunctionData data{ json }; if (std::filesystem::exists(data.Path)) { diff --git a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp @@ -0,0 +1,208 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscTestJsonResource.h" +#include "DscComposableObject.h" +#include <AppInstallerRuntime.h> + +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI +{ + namespace anon + { + 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."); + + using TestJsonObject = DscComposableObject<StandardExistProperty, PropertyProperty, ValueProperty>; + + struct TestJsonFunctionData + { + TestJsonFunctionData() + { + InitializeFileData(); + } + + TestJsonFunctionData(const std::optional<Json::Value>& json) : Input(json), Output(Input.CopyForOutput()) + { + InitializeFileData(); + } + + TestJsonObject Input; + TestJsonObject Output; + std::filesystem::path FilePath; + Json::Value RootValue; + + static std::filesystem::path GetFilePath() + { + std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); + result /= "test-json-file.json"; + return result; + } + + // Fills the Output object with the current state + void Get() + { + const std::string& propertyName = Input.Property().value(); + const Json::Value* propertyValue = RootValue.find(propertyName.data(), propertyName.data() + propertyName.length()); + + if (propertyValue) + { + Output.Exist(true); + + Output.Value(*propertyValue); + } + else + { + Output.Exist(false); + } + } + + private: + void InitializeFileData() + { + FilePath = GetFilePath(); + RootValue = GetJsonFromFile(); + } + + Json::Value GetJsonFromFile() const + { + Json::Value result; + Json::CharReaderBuilder builder; + Json::String errors; + + std::ifstream stream{ FilePath, std::ios::binary }; + + if (stream) + { + if (!Json::parseFromStream(builder, stream, &result, &errors)) + { + AICLI_LOG(CLI, Warning, << "Failed to read test JSON file: " << errors); + result = Json::Value{}; + } + } + else + { + AICLI_LOG(CLI, Warning, << "Couldn't open test JSON file: " << FilePath); + } + + return result; + } + }; + } + + DscTestJsonResource::DscTestJsonResource(std::string_view parent) : + DscCommandBase(parent, "test-json", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsState) + { + } + + std::vector<Argument> DscTestJsonResource::GetArguments() const + { + auto result = DscCommandBase::GetArguments(); + result.emplace_back(Execution::Args::Type::DscResourceFunctionDelete, Resource::String::DscResourceFunctionDescriptionDelete, ArgumentType::Flag); + return result; + } + + Resource::LocString DscTestJsonResource::ShortDescription() const + { + return "[TEST] JSON content resource"_lis; + } + + Resource::LocString DscTestJsonResource::LongDescription() const + { + return "[TEST] This resource is only available for tests. It provides JSON content configuration of a well known file."_lis; + } + + void DscTestJsonResource::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionDelete)) + { + std::filesystem::remove_all(anon::TestJsonFunctionData::GetFilePath()); + return; + } + + DscCommandBase::ExecuteInternal(context); + } + + std::string DscTestJsonResource::ResourceType() const + { + return "TestJSON"; + } + + void DscTestJsonResource::ResourceFunctionGet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + anon::TestJsonFunctionData data{ json }; + + data.Get(); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscTestJsonResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + anon::TestJsonFunctionData data{ json }; + + data.Get(); + + if (data.RootValue.isNull()) + { + data.RootValue = Json::Value{ Json::objectValue }; + } + + if (data.Input.ShouldExist()) + { + data.RootValue[data.Input.Property().value()] = data.Input.Value().value_or(Json::Value{ Json::nullValue }); + data.Output.Exist(true); + data.Output.Value(data.RootValue[data.Input.Property().value()]); + } + else if (data.Output.Exist().value()) + { + data.RootValue.removeMember(data.Input.Property().value()); + data.Output.Exist(false); + } + + std::ofstream stream{ data.FilePath, std::ios::binary }; + + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = " "; + + stream << Json::writeString(writerBuilder, data.RootValue); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscTestJsonResource::ResourceFunctionExport(Execution::Context& context) const + { + anon::TestJsonFunctionData data; + + if (data.RootValue.isObject()) + { + for (const auto& member : data.RootValue.getMemberNames()) + { + const Json::Value* memberValue = data.RootValue.find(member.data(), member.data() + member.length()); + + if (memberValue) + { + anon::TestJsonObject output; + output.Property(member); + output.Value(*memberValue); + + WriteJsonOutputLine(context, output.ToJson()); + } + } + } + } + + void DscTestJsonResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, anon::TestJsonObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscTestJsonResource.h b/src/AppInstallerCLICore/Commands/DscTestJsonResource.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A test resource implementing JSON content configuration of a well known file. + // This is exists to enable an input-less export, which is required in DSC v3.0.0 + struct DscTestJsonResource : public DscCommandBase + { + DscTestJsonResource(std::string_view parent); + + std::vector<Argument> GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(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/TestCommand.cpp b/src/AppInstallerCLICore/Commands/TestCommand.cpp @@ -6,6 +6,12 @@ #include "TestCommand.h" #include "AppInstallerRuntime.h" +#include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include "Workflows/ConfigurationFlow.h" +#include <winrt/Microsoft.Management.Configuration.h> + +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Utility::literals; namespace AppInstaller::CLI { @@ -70,12 +76,78 @@ namespace AppInstaller::CLI return hr; } + + void EnsureDSCv3Processor(Execution::Context& context) + { + auto& configurationSet = context.Get<Execution::Data::ConfigurationContext>().Set(); + configurationSet.Environment().ProcessorIdentifier(L"dscv3"); + } + + void InvokeGetAllUnits(Execution::Context& context) + { + auto& configurationContext = context.Get<Execution::Data::ConfigurationContext>(); + + winrt::Microsoft::Management::Configuration::ConfigurationUnit unit; + unit.Type(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ConfigurationExportResource))); + + auto result = configurationContext.Processor().GetAllUnits(unit); + + if (FAILED(result.ResultInformation().ResultCode())) + { + context.Reporter.Error() << "Failed to export: " << WINGET_OSTREAM_FORMAT_HRESULT(result.ResultInformation().ResultCode()) << std::endl; + AICLI_TERMINATE_CONTEXT(result.ResultInformation().ResultCode()); + } + + for (const auto& resultUnit : result.Units()) + { + configurationContext.Set().Units().Append(resultUnit); + } + } + + // Command to directly invoke the export flow. + struct TestConfigurationExportCommand final : public Command + { + TestConfigurationExportCommand(std::string_view parent) : Command("config-export-units", {}, parent) {} + + std::vector<Argument> GetArguments() const override + { + return { + Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, true }, + Argument{ Execution::Args::Type::ConfigurationExportResource, Resource::String::ConfigureExportResource }, + }; + } + + Resource::LocString ShortDescription() const override + { + return "Run config export"_lis; + } + + Resource::LocString LongDescription() const override + { + return "Runs the GetAllUnits configuration method to test export on a DSC v3 directly."_lis; + } + + protected: + void ExecuteInternal(Execution::Context& context) const override + { + context << + VerifyIsFullPackage << + CreateConfigurationProcessorWithoutFactory << + CreateOrOpenConfigurationSet{ "0.3" } << + EnsureDSCv3Processor << + CreateConfigurationProcessor << + InvokeGetAllUnits << + WriteConfigFile; + } + }; } std::vector<std::unique_ptr<Command>> TestCommand::GetCommands() const { - return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({ - std::make_unique<TestAppShutdownCommand>(FullName()), + return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>( + { + std::make_unique<TestAppShutdownCommand>(FullName()), + std::make_unique<TestConfigurationExportCommand>(FullName()), }); } diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -379,7 +379,7 @@ namespace AppInstaller::CLI::ConfigurationRemoting { case PropertyName::DscExecutablePath: return L"DscExecutablePath"; case PropertyName::FoundDscExecutablePath: return L"FoundDscExecutablePath"; - case PropertyName::DiagnosticTraceLevel: return L"DiagnosticTraceLevel"; + case PropertyName::DiagnosticTraceEnabled: return L"DiagnosticTraceEnabled"; } THROW_HR(E_UNEXPECTED); diff --git a/src/AppInstallerCLICore/ConfigureExportCommand.cpp b/src/AppInstallerCLICore/ConfigureExportCommand.cpp @@ -46,7 +46,7 @@ namespace AppInstaller::CLI VerifyIsFullPackage << SearchSourceForPackageExport << CreateConfigurationProcessorWithoutFactory << - CreateOrOpenConfigurationSet << + CreateOrOpenConfigurationSet{} << CreateConfigurationProcessor << PopulateConfigurationSetForExport << WriteConfigFile; diff --git a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h @@ -37,7 +37,7 @@ namespace AppInstaller::CLI::ConfigurationRemoting FoundDscExecutablePath, // Whether to request detailed traces from the processor. // Read / Write - DiagnosticTraceLevel, + DiagnosticTraceEnabled, }; // Gets the string for a property name. diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -148,7 +148,7 @@ namespace AppInstaller::CLI::Workflow if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose)) { - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceLevel), L"True"); + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled), L"True"); } } @@ -1405,7 +1405,7 @@ namespace AppInstaller::CLI::Workflow } } - void CreateOrOpenConfigurationSet(Context& context) + void CreateOrOpenConfigurationSet::operator()(Context& context) const { std::string argPath{ context.Args.GetArg(Args::Type::OutputFile) }; @@ -1415,9 +1415,8 @@ namespace AppInstaller::CLI::Workflow } else { - // TODO: support other schema versions or pick up latest. ConfigurationSet set; - set.SchemaVersion(L"0.2"); + set.SchemaVersion(Utility::ConvertToUTF16(m_defaultSchemaVersion)); std::wstring argPathWide = Utility::ConvertToUTF16(argPath); auto absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPathWide }); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h @@ -27,7 +27,15 @@ namespace AppInstaller::CLI::Workflow // Required Args: OutputFile // Inputs: ConfigurationProcessor // Outputs: ConfigurationSet - void CreateOrOpenConfigurationSet(Execution::Context& context); + struct CreateOrOpenConfigurationSet : public WorkflowTask + { + CreateOrOpenConfigurationSet(std::string defaultSchemaVersion = "0.2") : WorkflowTask("CreateOrOpenConfigurationSet"), m_defaultSchemaVersion(std::move(defaultSchemaVersion)) {} + + void operator()(Execution::Context& context) const override; + + private: + std::string m_defaultSchemaVersion; + }; // Outputs the configuration set. // Required Args: None diff --git a/src/AppInstallerCLIE2ETests/ConfigureCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureCommand.cs @@ -30,6 +30,9 @@ namespace AppInstallerCLIE2ETests 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); } /// <summary> @@ -305,6 +308,46 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(1, lines.Length); } + /// <summary> + /// Export all with specific package id. + /// </summary> + [Test] + public void DSCv3_Export() + { + // Reset state + var result = TestCommon.RunAICLICommand("dscv3 test-json", "--delete"); + Assert.AreEqual(0, result.ExitCode); + + // Configure properties + string propertyName1 = "prop1"; + string propertyName2 = "prop2"; + string propertyValue1 = "val1"; + string propertyValue2 = "val2"; + + string propertySetFormatString = "{{ \"property\": \"{0}\", \"value\": \"{1}\" }}"; + + result = TestCommon.RunAICLICommand("dscv3 test-json", "--set", string.Format(propertySetFormatString, propertyName1, propertyValue1)); + Assert.AreEqual(0, result.ExitCode); + + result = TestCommon.RunAICLICommand("dscv3 test-json", "--set", string.Format(propertySetFormatString, propertyName2, propertyValue2)); + Assert.AreEqual(0, result.ExitCode); + + // Export + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + + result = TestCommon.RunAICLICommand("test config-export-units", $"-o {exportFile} --resource Microsoft.WinGet/TestJSON --verbose"); + Assert.AreEqual(0, result.ExitCode); + + Assert.True(File.Exists(exportFile)); + string exportText = File.ReadAllText(exportFile); + Assert.True(exportText.Contains("Microsoft.WinGet/TestJSON")); + Assert.True(exportText.Contains(propertyName1)); + Assert.True(exportText.Contains(propertyName2)); + Assert.True(exportText.Contains(propertyValue1)); + Assert.True(exportText.Contains(propertyValue2)); + } + private void DeleteResourceArtifacts() { // Delete all .txt files in the test directory; they are placed there by the tests diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -1104,6 +1104,7 @@ namespace AppInstallerCLIE2ETests.Helpers if (!string.IsNullOrEmpty(stdIn)) { p.StandardInput.Write(stdIn); + p.StandardInput.Close(); } if (p.WaitForExit(timeOut)) diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs @@ -101,7 +101,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers /// <summary> /// Gets or sets a value indicating whether the processor should produce more verbose output. /// </summary> - public bool DiagnosticTraceLevel { get; set; } = false; + public bool DiagnosticTraceEnabled { get; set; } = false; /// <summary> /// Find the DSC v3 executable. @@ -132,7 +132,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers ProcessorSettings result = new ProcessorSettings(); result.DscExecutablePath = this.DscExecutablePath; - result.DiagnosticTraceLevel = this.DiagnosticTraceLevel; + result.DiagnosticTraceEnabled = this.DiagnosticTraceEnabled; #if !AICLI_DISABLE_TEST_HOOKS result.dscV3 = this.DSCv3; #endif @@ -152,7 +152,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers sb.AppendLine(this.EffectiveDscExecutablePath); sb.Append("DiagnosticTraceLevel: "); - sb.Append(this.DiagnosticTraceLevel); + sb.Append(this.DiagnosticTraceEnabled); return sb.ToString(); } diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IDSCv3.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IDSCv3.cs @@ -6,6 +6,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Model { + using System.Collections.Generic; using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; using Microsoft.Management.Configuration.Processor.Helpers; @@ -56,5 +57,13 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Model /// <param name="diagnosticsSink">The diagnostics sink if provided.</param> /// <returns>A set result.</returns> public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, IDiagnosticsSink? diagnosticsSink = null); + + /// <summary> + /// Exports configuration unit. + /// </summary> + /// <param name="unitInternal">The unit to export.</param> + /// <param name="diagnosticsSink">The diagnostics sink if provided.</param> + /// <returns>A list of export results.</returns> + public IList<IResourceExportItem> ExportResource(ConfigurationUnitInternal unitInternal, IDiagnosticsSink? diagnosticsSink = null); } } diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceExportItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceExportItem.cs @@ -0,0 +1,42 @@ +// ----------------------------------------------------------------------------- +// <copyright file="IResourceExportItem.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + using System.Collections.Generic; + using Windows.Foundation.Collections; + + /// <summary> + /// The interface to a `resource export` command result. + /// </summary> + internal interface IResourceExportItem + { + /// <summary> + /// Gets the type of the resource. + /// </summary> + public string Type { get; } + + /// <summary> + /// Gets the name of the resource instance. + /// </summary> + public string Name { get; } + + /// <summary> + /// Gets the settings for this item. + /// </summary> + public ValueSet Settings { get; } + + /// <summary> + /// Gets the metadata for this item. + /// </summary> + public ValueSet Metadata { get; } + + /// <summary> + /// Gets the dependencies for this item. + /// </summary> + public IList<string> Dependencies { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/DSCv3.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/DSCv3.cs @@ -7,6 +7,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04 { using System; + using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; @@ -29,6 +30,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04 private const string TestCommand = "test"; private const string GetCommand = "get"; private const string SetCommand = "set"; + private const string ExportCommand = "export"; private const string ResourceParameter = "-r"; private const string FileParameter = "-f"; private const string StdInputIdentifier = "-"; @@ -48,7 +50,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04 { get { - return this.processorSettings.DiagnosticTraceLevel ? DiagnosticTraceLevelArguments : string.Empty; + return this.processorSettings.DiagnosticTraceEnabled ? DiagnosticTraceLevelArguments : string.Empty; } } @@ -125,6 +127,30 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04 return SetFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions()); } + /// <inheritdoc /> + public IList<IResourceExportItem> ExportResource(ConfigurationUnitInternal unitInternal, IDiagnosticsSink? diagnosticsSink = null) + { + // 3.0 can't handle input to export; 3.1 will fix that. + ValueSet expandedSettings = unitInternal.GetExpandedSettings(); + if (expandedSettings.Count != 0) + { + throw new NotImplementedException("Must use DSC v3.1.* to provide input to export."); + } + + ProcessExecution processExecution = new ProcessExecution() + { + ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, + Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ExportCommand, ResourceParameter, unitInternal.QualifiedName }, + }; + + if (RunSynchronously(processExecution, diagnosticsSink)) + { + throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Export, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); + } + + return ConfigurationDocument.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions()).InterfaceResources; + } + /// <summary> /// Runs the process, waiting until it completes. /// </summary> diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ConfigurationDocument.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ConfigurationDocument.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationDocument.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// <summary> + /// A configuration document. + /// </summary> + internal class ConfigurationDocument + { + /// <summary> + /// Gets or sets the list of resources in the document. + /// </summary> + public List<ResourceItem> Resources { get; set; } = new List<ResourceItem>(); + + /// <summary> + /// Gets the list of resources as the interface version. + /// </summary> + [JsonIgnore] + public IList<IResourceExportItem> InterfaceResources + { + get + { + return new List<IResourceExportItem>(this.Resources.AsEnumerable<IResourceExportItem>()); + } + } + + /// <summary> + /// Initializes a new instance of the ConfigurationDocument class. + /// </summary> + /// <param name="document">The document to construct from.</param> + /// <param name="options">The options to use.</param> + /// <returns>The item created.</returns> + public static ConfigurationDocument CreateFrom(JsonDocument document, JsonSerializerOptions options) + { + ConfigurationDocument? result = JsonSerializer.Deserialize<ConfigurationDocument>(document, options); + + if (result == null) + { + throw new InvalidDataException("Unable to deserialize ConfigurationDocument."); + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceItem.cs @@ -0,0 +1,81 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ResourceItem.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Collections.Generic; + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Extensions; + using Windows.Foundation.Collections; + + /// <summary> + /// The object type from a single resource item. + /// </summary> + internal class ResourceItem : IResourceExportItem + { + /// <summary> + /// Gets or sets the type of the resource. + /// Should match the regex "^\\w+(\\.\\w+){0,2}\\/\\w+$". + /// </summary> + [JsonRequired] + required public string Type { get; set; } + + /// <summary> + /// Gets or sets the name of the resource instance. + /// </summary> + [JsonRequired] + required public string Name { get; set; } + + /// <summary> + /// Gets or sets the properties object. + /// </summary> + public JsonObject? Properties { get; set; } + + /// <summary> + /// Gets or sets the metadata object. + /// </summary> + [JsonPropertyName("metadata")] + public JsonObject? MetadataObject { get; set; } + + /// <summary> + /// Gets or sets the dependencies. + /// </summary> + [JsonPropertyName("dependencies")] + public List<string> DependenciesList { get; set; } = new List<string>(); + + /// <inheritdoc /> + [JsonIgnore] + public ValueSet Settings + { + get + { + return this.Properties.ToValueSet(); + } + } + + /// <inheritdoc /> + [JsonIgnore] + public ValueSet Metadata + { + get + { + return this.MetadataObject.ToValueSet(); + } + } + + /// <inheritdoc /> + [JsonIgnore] + public IList<string> Dependencies + { + get + { + return this.DependenciesList; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Unit/DSCv3ConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Unit/DSCv3ConfigurationUnitProcessor.cs @@ -6,8 +6,11 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Unit { + using System; + using System.Collections.Generic; using Microsoft.Management.Configuration; using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.Exceptions; using Microsoft.Management.Configuration.Processor.Helpers; using Microsoft.Management.Configuration.Processor.Unit; using Windows.Foundation.Collections; @@ -15,7 +18,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Unit /// <summary> /// Provides access to a specific configuration unit within the runtime. /// </summary> - internal sealed partial class DSCv3ConfigurationUnitProcessor : ConfigurationUnitProcessorBase, IConfigurationUnitProcessor, IDiagnosticsSink + internal sealed partial class DSCv3ConfigurationUnitProcessor : ConfigurationUnitProcessorBase, IConfigurationUnitProcessor, IGetAllSettingsConfigurationUnitProcessor, IGetAllUnitsConfigurationUnitProcessor, IDiagnosticsSink { private readonly ProcessorSettings processorSettings; @@ -54,5 +57,49 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Unit { return this.processorSettings.DSCv3.SetResourceSettings(this.UnitInternal, this).RebootRequired; } + + /// <inheritdoc /> + protected override IList<ValueSet>? GetAllSettingsInternal() + { + var exportResult = this.processorSettings.DSCv3.ExportResource(this.UnitInternal, this); + + string expectedType = this.UnitInternal.QualifiedName.ToLowerInvariant(); + List<ValueSet> result = new List<ValueSet>(); + + foreach (var exportItem in exportResult) + { + if (exportItem.Type.ToLowerInvariant() != expectedType) + { + throw new UnitPropertyUnsupportedException(typeof(IGetAllSettingsConfigurationUnitProcessor)); + } + + result.Add(exportItem.Settings); + } + + return result; + } + + /// <inheritdoc /> + protected override IList<ConfigurationUnit>? GetAllUnitsInternal() + { + var exportResult = this.processorSettings.DSCv3.ExportResource(this.UnitInternal, this); + + List<ConfigurationUnit> result = new List<ConfigurationUnit>(); + + foreach (var exportItem in exportResult) + { + ConfigurationUnit unit = new ConfigurationUnit(); + + unit.Type = exportItem.Type; + unit.Identifier = exportItem.Name; + unit.Settings = exportItem.Settings; + unit.Metadata = exportItem.Metadata; + unit.Dependencies = exportItem.Dependencies; + + result.Add(unit); + } + + return result; + } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceException.cs @@ -32,6 +32,11 @@ namespace Microsoft.Management.Configuration.Processor.Exceptions public const string Test = "Test"; /// <summary> + /// The string for the Export method. + /// </summary> + public const string Export = "Export"; + + /// <summary> /// Initializes a new instance of the <see cref="InvokeDscResourceException"/> class. /// Use this constructor when no error is generated by the invoke and the result is not a valid value. /// </summary> @@ -159,6 +164,7 @@ namespace Microsoft.Management.Configuration.Processor.Exceptions case Get: return ErrorCodes.WinGetConfigUnitInvokeGet; case Set: return ErrorCodes.WinGetConfigUnitInvokeSet; case Test: return ErrorCodes.WinGetConfigUnitInvokeTest; + case Export: return ErrorCodes.WinGetConfigUnitInvokeGet; } return ErrorCodes.Unexpected; diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/JsonObjectExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/JsonObjectExtensions.cs @@ -20,13 +20,16 @@ namespace Microsoft.Management.Configuration.Processor.Extensions /// </summary> /// <param name="jsonObject">The object to convert.</param> /// <returns>The ValueSet.</returns> - public static ValueSet ToValueSet(this JsonObject jsonObject) + public static ValueSet ToValueSet(this JsonObject? jsonObject) { ValueSet result = new ValueSet(); - foreach (var item in jsonObject) + if (jsonObject != null) { - result.Add(item.Key, ToValue(item.Value)); + foreach (var item in jsonObject) + { + result.Add(item.Key, ToValue(item.Value)); + } } return result; diff --git a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs @@ -22,7 +22,7 @@ namespace Microsoft.Management.Configuration.Processor { private const string DscExecutablePathPropertyName = "DscExecutablePath"; private const string FoundDscExecutablePathPropertyName = "FoundDscExecutablePath"; - private const string DiagnosticTraceLevelPropertyName = "DiagnosticTraceLevel"; + private const string DiagnosticTraceEnabledPropertyName = "DiagnosticTraceEnabled"; private ProcessorSettings processorSettings = new (); @@ -155,8 +155,8 @@ namespace Microsoft.Management.Configuration.Processor case FoundDscExecutablePathPropertyName: value = ProcessorSettings.FindDscExecutablePath() !; return true; - case DiagnosticTraceLevelPropertyName: - value = this.processorSettings.DiagnosticTraceLevel.ToString(); + case DiagnosticTraceEnabledPropertyName: + value = this.processorSettings.DiagnosticTraceEnabled.ToString(); return true; } @@ -194,8 +194,8 @@ namespace Microsoft.Management.Configuration.Processor case DscExecutablePathPropertyName: this.DscExecutablePath = value; break; - case DiagnosticTraceLevelPropertyName: - this.processorSettings.DiagnosticTraceLevel = bool.Parse(value); + case DiagnosticTraceEnabledPropertyName: + this.processorSettings.DiagnosticTraceEnabled = bool.Parse(value); break; default: throw new ArgumentOutOfRangeException($"Invalid property name: {name}"); diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs @@ -7,6 +7,7 @@ namespace Microsoft.Management.Configuration.Processor.Unit { using System; + using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; using Microsoft.Management.Configuration; @@ -143,6 +144,55 @@ namespace Microsoft.Management.Configuration.Processor.Unit } /// <summary> + /// Gets the current settings for all the instances of a configuration unit. + /// </summary> + /// <returns>A <see cref="IGetAllSettingsResult"/>.</returns> + public IGetAllSettingsResult GetAllSettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `GetAllSettings` for resource: {this.unitInternal.QualifiedName}..."); + + this.CheckLimitMode(ConfigurationUnitIntent.Inform); + var result = new GetAllSettingsResult(this.Unit); + + try + { + result.Settings = this.GetAllSettingsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `GetAllSettings`."); + return result; + } + + /// <summary> + /// Gets all configuration units for the given unit type. + /// Returned units may be of types other than the one passed in. + /// </summary> + /// <returns>A <see cref="IGetAllUnitsResult"/>.</returns> + public IGetAllUnitsResult GetAllUnits() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `GetAllUnits` for resource: {this.unitInternal.QualifiedName}..."); + + this.CheckLimitMode(ConfigurationUnitIntent.Inform); + var result = new GetAllUnitsResult(this.Unit); + + try + { + result.Units = this.GetAllUnitsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `GetAllUnits`."); + return result; + } + + /// <summary> /// Gets the current settings. /// </summary> /// <returns>The current settings.</returns> @@ -161,6 +211,27 @@ namespace Microsoft.Management.Configuration.Processor.Unit protected abstract bool ApplySettingsInternal(); /// <summary> + /// Gets the current settings for all the instances of a configuration unit. + /// Derive from IGetAllSettingsConfigurationUnitProcessor and implement an override to support this. + /// </summary> + /// <returns>The settings as ValueSets.</returns> + protected virtual IList<ValueSet>? GetAllSettingsInternal() + { + throw new NotImplementedException("Configuration unit processor did not implement GetAllSettingsInternal."); + } + + /// <summary> + /// Gets all configuration units for the given unit type. + /// Returned units may be of types other than the one passed in. + /// Derive from IGetAllUnitsConfigurationUnitProcessor and implement an override to support this. + /// </summary> + /// <returns>The configuration units.</returns> + protected virtual IList<ConfigurationUnit>? GetAllUnitsInternal() + { + throw new NotImplementedException("Configuration unit processor did not implement GetAllUnitsInternal."); + } + + /// <summary> /// Sends diagnostics if appropriate. /// </summary> /// <param name="level">The level of this diagnostic message.</param> diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/GetAllUnitsResult.cs b/src/Microsoft.Management.Configuration.Processor/Unit/GetAllUnitsResult.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------------- +// <copyright file="GetAllUnitsResult.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Windows.Foundation.Collections; + + /// <summary> + /// Implements IGetAllUnitsResult. + /// </summary> + internal partial class GetAllUnitsResult : IGetAllUnitsResult + { + /// <summary> + /// Initializes a new instance of the <see cref="GetAllUnitsResult"/> class. + /// </summary> + /// <param name="unit">The configuration unit that the result is for.</param> + public GetAllUnitsResult(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// <summary> + /// Gets the configuration unit that the result is for. + /// </summary> + public ConfigurationUnit Unit { get; private set; } + + /// <inheritdoc/> + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// <summary> + /// Gets the implementation object for ResultInformation. + /// </summary> + public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); + + /// <inheritdoc/> + public IList<ConfigurationUnit>? Units { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestDSCv3.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestDSCv3.cs @@ -6,6 +6,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers { + using System.Collections.Generic; using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; using Microsoft.Management.Configuration.Processor.DSCv3.Model; using Microsoft.Management.Configuration.Processor.Helpers; @@ -44,6 +45,13 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers internal delegate IResourceTestItem TestResourceDelegateType(ConfigurationUnitInternal unitInternal); /// <summary> + /// The delegate type for TestResource. + /// </summary> + /// <param name="unitInternal">The unit to test.</param> + /// <returns>A test result.</returns> + internal delegate IList<IResourceExportItem> ExportResourceDelegateType(ConfigurationUnitInternal unitInternal); + + /// <summary> /// Gets or sets the GetResourceByType result. /// </summary> public IResourceListItem? GetResourceByTypeResult { get; set; } @@ -83,6 +91,16 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers /// </summary> public TestResourceDelegateType? TestResourceDelegate { get; set; } + /// <summary> + /// Gets or sets the ExportResource result. + /// </summary> + public IList<IResourceExportItem>? ExportResourceResult { get; set; } + + /// <summary> + /// Gets or sets the ExportResource delegate. + /// </summary> + public ExportResourceDelegateType? ExportResourceDelegate { get; set; } + /// <inheritdoc/> public IResourceListItem? GetResourceByType(string resourceType, IDiagnosticsSink? diagnosticsSink = null) { @@ -106,5 +124,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers { return this.TestResourceResult ?? this.TestResourceDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); } + + /// <inheritdoc/> + public IList<IResourceExportItem> ExportResource(ConfigurationUnitInternal unitInternal, IDiagnosticsSink? diagnosticsSink = null) + { + return this.ExportResourceResult ?? this.ExportResourceDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); + } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceExportItem.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceExportItem.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------------- +// <copyright file="TestResourceExportItem.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Windows.Foundation.Collections; + + /// <summary> + /// Implements IResourceExportItem for tests. + /// </summary> + internal class TestResourceExportItem : IResourceExportItem + { + /// <summary> + /// Gets or sets the type. + /// </summary> + required public string Type { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + required public string Name { get; set; } + + /// <summary> + /// Gets or sets the settings. + /// </summary> + public ValueSet Settings { get; set; } = new ValueSet(); + + /// <summary> + /// Gets or sets the metadata. + /// </summary> + public ValueSet Metadata { get; set; } = new ValueSet(); + + /// <summary> + /// Gets or sets the dependencies. + /// </summary> + public IList<string> Dependencies { get; set; } = new List<string>(); + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs @@ -6,10 +6,14 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests { + using System.Collections.Generic; + using System.Linq; using Microsoft.Management.Configuration.Processor; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; using Microsoft.Management.Configuration.Processor.Exceptions; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; + using Windows.Foundation.Collections; using Xunit; using Xunit.Abstractions; @@ -109,6 +113,201 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Equal(type1, unitProcessor.Unit.Type); } + /// <summary> + /// Test for settings export. + /// </summary> + [Fact] + public void GetAllSettings_Expected() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + dsc.ExportResourceResult = new List<IResourceExportItem>() + { + new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, + new TestResourceExportItem() { Type = type1, Name = "2", Settings = set2 }, + }; + + var result = processor.GetAllUnitSettings(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + + Assert.Equal(2, result.Settings.Count); + Assert.NotNull(result.Settings.Single(set => set.Contains(set1.First()))); + Assert.NotNull(result.Settings.Single(set => set.Contains(set2.First()))); + } + + /// <summary> + /// Test for settings export with differing types. + /// </summary> + [Fact] + public void GetAllSettings_DifferentType() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + string type2 = "Type2"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + dsc.ExportResourceResult = new List<IResourceExportItem>() + { + new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, + new TestResourceExportItem() { Type = type2, Name = "2", Settings = set2 }, + }; + + var result = processor.GetAllUnitSettings(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.NotNull(result.ResultInformation.ResultCode); + Assert.Equal(ErrorCodes.WinGetConfigUnitUnsupportedType, result.ResultInformation.ResultCode.HResult); + } + + /// <summary> + /// Test for unit export. + /// </summary> + [Fact] + public void GetAllUnits_Simple() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + dsc.ExportResourceResult = new List<IResourceExportItem>() + { + new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, + new TestResourceExportItem() { Type = type1, Name = "2", Settings = set2 }, + }; + + var result = processor.GetAllUnits(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + + Assert.Equal(2, result.Units.Count); + + foreach (var unit in result.Units) + { + Assert.Equal(type1, unit.Type); + Assert.NotEmpty(unit.Identifier); + } + + Assert.NotEqual(result.Units[0].Identifier, result.Units[1].Identifier); + + Assert.NotNull(result.Units.Single(unit => unit.Settings.Contains(set1.First()))); + Assert.NotNull(result.Units.Single(unit => unit.Settings.Contains(set2.First()))); + } + + /// <summary> + /// Test for unit export with complex data. + /// </summary> + [Fact] + public void GetAllUnits_Complex() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + string type2 = "Type2"; + + string name1 = "1"; + string name2 = "2"; + + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet metadata1 = new ValueSet(); + metadata1.Add("met1", "val11"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + List<string> dependencies2 = new List<string>(); + dependencies2.Add(name1); + + dsc.ExportResourceResult = new List<IResourceExportItem>() + { + new TestResourceExportItem() { Type = type1, Name = name1, Settings = set1, Metadata = metadata1 }, + new TestResourceExportItem() { Type = type2, Name = name2, Settings = set2, Dependencies = dependencies2 }, + }; + + var result = processor.GetAllUnits(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + + Assert.Equal(2, result.Units.Count); + + var result1 = result.Units.Single(unit => unit.Identifier == name1); + var result2 = result.Units.Single(unit => unit.Identifier == name2); + + Assert.Equal(type1, result1.Type); + Assert.Equal(type2, result2.Type); + + Assert.Contains(set1.First(), result1.Settings); + Assert.Contains(set2.First(), result2.Settings); + + Assert.Contains(metadata1.First(), result1.Metadata); + Assert.Empty(result2.Metadata); + + Assert.Empty(result1.Dependencies); + Assert.Contains(dependencies2.First(), result2.Dependencies); + } + private static (DSCv3ConfigurationSetProcessorFactory, TestDSCv3) CreateTestFactory() { DSCv3ConfigurationSetProcessorFactory factory = new DSCv3ConfigurationSetProcessorFactory(); diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp @@ -14,6 +14,7 @@ #include "ConfigurationUnitResultInformation.h" #include "GetConfigurationUnitSettingsResult.h" #include "GetAllConfigurationUnitSettingsResult.h" +#include "GetAllConfigurationUnitsResult.h" #include "ExceptionResultHelpers.h" #include "ConfigurationSetChangeData.h" #include "GetConfigurationUnitDetailsResult.h" @@ -804,7 +805,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation try { - // TODO: Directives overlay to prevent running elevated for test unitProcessor = setProcessor.CreateUnitProcessor(unit); } catch (...) @@ -841,6 +841,123 @@ namespace winrt::Microsoft::Management::Configuration::implementation return *result; } + Configuration::GetAllConfigurationUnitsResult ConfigurationProcessor::GetAllUnits(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return GetAllUnitsImpl(unit); + } + + Windows::Foundation::IAsyncOperation<Configuration::GetAllConfigurationUnitsResult> ConfigurationProcessor::GetAllUnitsAsync(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + ConfigurationUnit localUnit = unit; + + co_await winrt::resume_background(); + + co_return GetAllUnitsImpl(localUnit, { co_await winrt::get_cancellation_token() }); + } + + Configuration::GetAllConfigurationUnitsResult ConfigurationProcessor::GetAllUnitsImpl( + const ConfigurationUnit& unit, + AppInstaller::WinRT::AsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + auto result = make_self<wil::details::module_count_wrapper<implementation::GetAllConfigurationUnitsResult>>(); + auto unitResult = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>(); + result->ResultInformation(*unitResult); + + cancellation.ThrowIfCancelled(); + + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + cancellation.ThrowIfCancelled(); + + IGetAllUnitsConfigurationUnitProcessor getAllUnitsUnitProcessor; + IGetAllSettingsConfigurationUnitProcessor getAllSettingsUnitProcessor; + + if (unitProcessor.try_as<IGetAllUnitsConfigurationUnitProcessor>(getAllUnitsUnitProcessor)) + { + cancellation.ThrowIfCancelled(); + + try + { + IGetAllUnitsResult allUnitsResult = getAllUnitsUnitProcessor.GetAllUnits(); + result->Units(allUnitsResult.Units()); + result->ResultInformation(allUnitsResult.ResultInformation()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); + } + else if (unitProcessor.try_as<IGetAllSettingsConfigurationUnitProcessor>(getAllSettingsUnitProcessor)) + { + cancellation.ThrowIfCancelled(); + + try + { + IGetAllSettingsResult allSettingsResult = getAllSettingsUnitProcessor.GetAllSettings(); + + auto allSettings = allSettingsResult.Settings(); + if (allSettings) + { + std::vector<Configuration::ConfigurationUnit> units; + + size_t index = 0; + auto currentType = unit.Type(); + auto currentDetails = unit.Details(); + + for (const auto& settings : allSettings) + { + auto newUnit = make_self<implementation::ConfigurationUnit>(); + + newUnit->Type(currentType); + newUnit->Settings(settings); + newUnit->Details(currentDetails); + + std::wostringstream identifierStream; + identifierStream << static_cast<std::wstring_view>(currentType) << L'-' << index++; + newUnit->Identifier(hstring{ identifierStream.str() }); + + units.push_back(*newUnit); + } + + result->Units(single_threaded_vector(std::move(units))); + } + + result->ResultInformation(allSettingsResult.ResultInformation()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); + } + else + { + AICLI_LOG(Config, Error, << "Unit Processor does not support GetAllUnits or GetAllSettings operation"); + unitResult->Initialize(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, hstring{}); + } + + return *result; + } + IConfigurationGroupProcessor ConfigurationProcessor::GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet) { IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationProcessor.h @@ -84,6 +84,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation GetAllConfigurationUnitSettingsResult GetAllUnitSettings(const ConfigurationUnit& unit); Windows::Foundation::IAsyncOperation<GetAllConfigurationUnitSettingsResult> GetAllUnitSettingsAsync(const ConfigurationUnit& unit); + Configuration::GetAllConfigurationUnitsResult GetAllUnits(const ConfigurationUnit& unit); + Windows::Foundation::IAsyncOperation<Configuration::GetAllConfigurationUnitsResult> GetAllUnitsAsync(const ConfigurationUnit& unit); + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) @@ -121,6 +124,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation GetAllConfigurationUnitSettingsResult GetAllUnitSettingsImpl(const ConfigurationUnit& unit, AppInstaller::WinRT::AsyncCancellation cancellation = {}); + Configuration::GetAllConfigurationUnitsResult GetAllUnitsImpl(const ConfigurationUnit& unit, AppInstaller::WinRT::AsyncCancellation cancellation = {}); + IConfigurationGroupProcessor GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet); void SendDiagnosticsImpl(const IDiagnosticInformation& information); diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp @@ -131,7 +131,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation return m_details; } - void ConfigurationUnit::Details(IConfigurationUnitProcessorDetails&& details) + void ConfigurationUnit::Details(IConfigurationUnitProcessorDetails details) { m_details = std::move(details); } diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.h b/src/Microsoft.Management.Configuration/ConfigurationUnit.h @@ -63,7 +63,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Dependencies(std::vector<hstring>&& value); - void Details(IConfigurationUnitProcessorDetails&& details); + void Details(IConfigurationUnitProcessorDetails details); void Units(std::vector<Configuration::ConfigurationUnit>&& value); private: diff --git a/src/Microsoft.Management.Configuration/GetAllConfigurationUnitsResult.cpp b/src/Microsoft.Management.Configuration/GetAllConfigurationUnitsResult.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "GetAllConfigurationUnitsResult.h" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + GetAllConfigurationUnitsResult::GetAllConfigurationUnitsResult() : + m_resultInformation(*make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()) + { + } + + void GetAllConfigurationUnitsResult::ResultInformation(const IConfigurationUnitResultInformation& resultInformation) + { + m_resultInformation = resultInformation; + } + + IConfigurationUnitResultInformation GetAllConfigurationUnitsResult::ResultInformation() const + { + return m_resultInformation; + } + + Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit> GetAllConfigurationUnitsResult::Units() + { + return m_units; + } + + void GetAllConfigurationUnitsResult::Units(Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>&& value) + { + m_units = std::move(value); + } +} diff --git a/src/Microsoft.Management.Configuration/GetAllConfigurationUnitsResult.h b/src/Microsoft.Management.Configuration/GetAllConfigurationUnitsResult.h @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "GetAllConfigurationUnitsResult.g.h" +#include <winrt/Windows.Foundation.Collections.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct GetAllConfigurationUnitsResult : GetAllConfigurationUnitsResultT<GetAllConfigurationUnitsResult> + { + GetAllConfigurationUnitsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void ResultInformation(const IConfigurationUnitResultInformation& resultInformation); + void Units(Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>&& value); +#endif + + IConfigurationUnitResultInformation ResultInformation() const; + Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit> Units(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + IConfigurationUnitResultInformation m_resultInformation; + Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit> m_units; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Configuration { - [contractversion(3)] + [contractversion(4)] apicontract Contract{}; // The current state of a configuration set. @@ -517,6 +517,21 @@ namespace Microsoft.Management.Configuration IConfigurationUnitResultInformation ResultInformation{ get; }; } + // The result of getting all of the units for a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 4)] + interface IGetAllUnitsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // The units retrieved for the given unit. + Windows.Foundation.Collections.IVector<ConfigurationUnit> Units{ get; }; + + // The result of getting the configuration units. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + // Provides access to a specific configuration unit within the runtime. [contract(Microsoft.Management.Configuration.Contract, 1)] interface IConfigurationUnitProcessor @@ -541,6 +556,13 @@ namespace Microsoft.Management.Configuration IGetAllSettingsResult GetAllSettings(); } + [contract(Microsoft.Management.Configuration.Contract, 4)] + interface IGetAllUnitsConfigurationUnitProcessor requires IConfigurationUnitProcessor + { + // Gets all units for the configuration unit. + IGetAllUnitsResult GetAllUnits(); + } + // Controls the lifetime of operations for a single configuration set. [contract(Microsoft.Management.Configuration.Contract, 1)] interface IConfigurationSetProcessor @@ -883,6 +905,18 @@ namespace Microsoft.Management.Configuration Windows.Foundation.Collections.IVector<Windows.Foundation.Collections.ValueSet> Settings { get; }; } + // The result of getting the all units for a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 4)] + runtimeclass GetAllConfigurationUnitsResult + { + // The result of getting the all units for a configuration unit. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // The units retrieved for the given unit. + Windows.Foundation.Collections.IVector<ConfigurationUnit> Units { get; }; + } + // The configuration processor is responsible for the interactions with the system. [contract(Microsoft.Management.Configuration.Contract, 1)] runtimeclass ConfigurationProcessor @@ -946,6 +980,14 @@ namespace Microsoft.Management.Configuration GetAllConfigurationUnitSettingsResult GetAllUnitSettings(ConfigurationUnit unit); Windows.Foundation.IAsyncOperation<GetAllConfigurationUnitSettingsResult> GetAllUnitSettingsAsync(ConfigurationUnit unit); } + + [contract(Microsoft.Management.Configuration.Contract, 4)] + { + // Gets all configuration units for the given unit type. + // Returned units may be of types other than the one passed in. + GetAllConfigurationUnitsResult GetAllUnits(ConfigurationUnit unit); + Windows.Foundation.IAsyncOperation<GetAllConfigurationUnitsResult> GetAllUnitsAsync(ConfigurationUnit unit); + } } // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -229,6 +229,7 @@ <ClInclude Include="ExceptionResultHelpers.h" /> <ClInclude Include="Filesystem.h" /> <ClInclude Include="GetAllConfigurationUnitSettingsResult.h" /> + <ClInclude Include="GetAllConfigurationUnitsResult.h" /> <ClInclude Include="GetConfigurationSetDetailsResult.h" /> <ClInclude Include="GetConfigurationUnitDetailsResult.h" /> <ClInclude Include="GetConfigurationUnitSettingsResult.h" /> @@ -284,6 +285,7 @@ <ClCompile Include="DiagnosticInformationInstance.cpp" /> <ClCompile Include="Filesystem.cpp" /> <ClCompile Include="GetAllConfigurationUnitSettingsResult.cpp" /> + <ClCompile Include="GetAllConfigurationUnitsResult.cpp" /> <ClCompile Include="GetConfigurationSetDetailsResult.cpp" /> <ClCompile Include="GetConfigurationUnitDetailsResult.cpp" /> <ClCompile Include="GetConfigurationUnitSettingsResult.cpp" /> diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -156,6 +156,9 @@ <ClCompile Include="ConfigurationEnvironment.cpp"> <Filter>API Source</Filter> </ClCompile> + <ClCompile Include="GetAllConfigurationUnitsResult.cpp"> + <Filter>API Source</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -321,6 +324,9 @@ <ClInclude Include="ConfigurationEnvironment.h"> <Filter>API Headers</Filter> </ClInclude> + <ClInclude Include="GetAllConfigurationUnitsResult.h"> + <Filter>API Headers</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Midl Include="Microsoft.Management.Configuration.idl" />