commit 34fbaabe25df5353d92333dbe2c968a5a2feb316 parent 995a95d9b2e56ddffcde0e3722d8f2e3c468a367 Author: JohnMcPMS <johnmcp@microsoft.com> Date: Thu, 25 Jan 2024 09:14:28 -0800 Support group processing of configurations (#4059) Adds support for test and apply processing to handle groups. This is done by changing the top-level processing to only work on groups, and moving the existing processing code to a default group processor that is used when the set processor does not expose the group interface. All telemetry is handled by the top level processing code so that we get consistent data regardless of the set processor. Both set and unit processors can implement `IConfigurationGroupProcessor` to indicate that they can handle group processing on their own. If the set processor does not expose this interface, the default processor will handle the set level units while checking the unit processors for group handling as well. If the set processor does expose the interface, it is expected to handle all processing. It is still necessary to implement unit processors to expose details about them, but they don't need to be able to function. Also changed most `winrt::single_threaded_vector` to `winrt::multi_threaded_vector` as it was appropriate for their potential usage. Diffstat:
43 files changed, 2075 insertions(+), 235 deletions(-)
diff --git a/src/AppInstallerCLICore/Commands/DebugCommand.cpp b/src/AppInstallerCLICore/Commands/DebugCommand.cpp @@ -94,6 +94,8 @@ namespace AppInstaller::CLI OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::GetConfigurationUnitDetailsResult>>(context); OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::ApplyConfigurationUnitResult>>(context); OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::TestConfigurationUnitResult>>(context); + OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::IApplyGroupMemberSettingsResult>>(context); + OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::ITestSettingsResult>>(context); // TODO: Fix the layering inversion created by the COM deployment API (probably in order to operate winget.exe against the COM server). // Then this code can just have a CppWinRT reference to the deployment API and spit out the interface registrations just like for configuration. diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -97,6 +97,8 @@ <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.GetConfigurationUnitDetailsResult>" InterfaceId="3A034399-0F2B-51C2-A9C5-4BC6E9940068" /> <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.ApplyConfigurationUnitResult>" InterfaceId="0E2334B9-8431-5A9D-B3AA-62D4FB5B5749" /> <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.TestConfigurationUnitResult>" InterfaceId="73848262-86D4-5FFC-8353-8408C4E649DE" /> + <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.IApplyGroupMemberSettingsResult>" InterfaceId="5086070C-F468-5B00-8352-50FB420BA8B0" /> + <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.ITestSettingsResult>" InterfaceId="2D28E6AA-7036-5D78-9B58-9456F1E332FE" /> </ProxyStub> </Extension> <!-- This entry forces the package registration to process the windows.activatableClass.proxyStub extension above. --> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupMemberSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupMemberSettingsResultInstance.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ApplyGroupMemberSettingsResultInstance.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + /// <summary> + /// Implements IApplyGroupMemberSettingsResult. + /// </summary> + internal sealed class ApplyGroupMemberSettingsResultInstance : IApplyGroupMemberSettingsResult + { + /// <summary> + /// Initializes a new instance of the <see cref="ApplyGroupMemberSettingsResultInstance"/> class. + /// </summary> + /// <param name="unit">The unit for this result.</param> + internal ApplyGroupMemberSettingsResultInstance(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// <inheritdoc/> + public bool PreviouslyInDesiredState { get; internal set; } + + /// <inheritdoc/> + public bool RebootRequired { get; internal set; } + + /// <inheritdoc/> + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// <summary> + /// Gets the implementation object for ResultInformation. + /// </summary> + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// <inheritdoc/> + public ConfigurationUnitState State { get; internal set; } + + /// <inheritdoc/> + public ConfigurationUnit Unit { get; private init; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupSettingsResultInstance.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ApplyGroupSettingsResultInstance.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + + /// <summary> + /// Implements IApplyGroupSettingsResult. + /// </summary> + internal sealed class ApplyGroupSettingsResultInstance : IApplyGroupSettingsResult + { + /// <summary> + /// Initializes a new instance of the <see cref="ApplyGroupSettingsResultInstance"/> class. + /// </summary> + /// <param name="group">The group for this result.</param> + internal ApplyGroupSettingsResultInstance(object? group) + { + this.Group = group; + } + + /// <inheritdoc/> + public object? Group { get; private init; } + + /// <inheritdoc/> + public bool RebootRequired { get; internal set; } + + /// <inheritdoc/> + public IConfigurationUnitResultInformation? ResultInformation + { + get { return this.InternalResult; } + } + + /// <summary> + /// Gets the implementation object for ResultInformation. + /// </summary> + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// <inheritdoc/> + public IList<IApplyGroupMemberSettingsResult>? UnitResults { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs @@ -145,6 +145,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers private static void SummaryCountByIntent(int[] counts, int[] runs, int[] failures, ConfigurationUnitIntent intent, IConfigurationUnitResultInformation resultInformation) { + if (intent == ConfigurationUnitIntent.Unknown) + { + intent = ConfigurationUnitIntent.Apply; + } + int index = (int)intent; counts[index]++; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs @@ -121,5 +121,17 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers this.Processors[configurationSet] = new TestConfigurationSetProcessor(configurationSet); return this.Processors[configurationSet]; } + + /// <summary> + /// A convenience function to create a new group processor for the given set and store it in the dictionary for use in the test. + /// </summary> + /// <param name="configurationSet">The set.</param> + /// <returns>A new TestConfigurationSetGroupProcessor for the set.</returns> + internal TestConfigurationSetGroupProcessor CreateTestGroupProcessor(ConfigurationSet configurationSet) + { + TestConfigurationSetGroupProcessor result = new (configurationSet); + this.Processors[configurationSet] = result; + return result; + } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetGroupProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetGroupProcessor.cs @@ -0,0 +1,118 @@ +// ----------------------------------------------------------------------------- +// <copyright file="TestConfigurationSetGroupProcessor.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.InteropServices.WindowsRuntime; + using System.Threading; + using System.Threading.Tasks; + using Windows.Foundation; + using Windows.Foundation.Collections; + + /// <summary> + /// A test implementation of IConfigurationGroupProcessor. + /// </summary> + internal class TestConfigurationSetGroupProcessor : TestConfigurationSetProcessor, IConfigurationGroupProcessor + { + /// <summary> + /// The event that is waited on before actually processing the async operations. + /// </summary> + private AutoResetEvent asyncWaitEvent = new AutoResetEvent(false); + + /// <summary> + /// Initializes a new instance of the <see cref="TestConfigurationSetGroupProcessor"/> class. + /// </summary> + /// <param name="set">The set that this processor is for.</param> + internal TestConfigurationSetGroupProcessor(ConfigurationSet? set) + : base(set) + { + } + + /// <summary> + /// Gets the group that this processor targets. + /// </summary> + public object? Group + { + get { return this.Set; } + } + + /// <summary> + /// Gets or sets a value indicating whether the async methods should wait on an event before processing. + /// </summary> + internal bool ShouldWaitOnAsyncEvent { get; set; } = false; + + /// <summary> + /// Apply settings for the group. + /// </summary> + /// <returns>The operation to apply settings.</returns> + public IAsyncOperationWithProgress<IApplyGroupSettingsResult, IApplyGroupMemberSettingsResult> ApplyGroupSettingsAsync() + { + return AsyncInfo.Run((CancellationToken cancellationToken, IProgress<IApplyGroupMemberSettingsResult> progress) => Task.Run<IApplyGroupSettingsResult>(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + ApplyGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List<IApplyGroupMemberSettingsResult>(); + + if (this.Set != null) + { + TestConfigurationUnitGroupProcessor.ApplyGroupSettings(this.Set.Units, progress, result); + } + + return result; + })); + } + + /// <summary> + /// Test settings for the group. + /// </summary> + /// <returns>The operation to test settings.</returns> + public IAsyncOperationWithProgress<ITestGroupSettingsResult, ITestSettingsResult> TestGroupSettingsAsync() + { + return AsyncInfo.Run((CancellationToken cancellationToken, IProgress<ITestSettingsResult> progress) => Task.Run<ITestGroupSettingsResult>(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + TestGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List<ITestSettingsResult>(); + + if (this.Set != null) + { + result.TestResult = TestConfigurationUnitGroupProcessor.GetTestResult(this.Set.Metadata); + TestConfigurationUnitGroupProcessor.TestGroupSettings(this.Set.Units, progress, result); + } + + return result; + })); + } + + /// <summary> + /// Signals the async event. + /// </summary> + internal void SignalAsyncEvent() + { + this.asyncWaitEvent.Set(); + } + + /// <summary> + /// Waits on the async event. + /// </summary> + private void WaitOnAsyncEvent(CancellationToken cancellationToken) + { + if (this.ShouldWaitOnAsyncEvent) + { + cancellationToken.Register(() => this.asyncWaitEvent.Set()); + if (!this.asyncWaitEvent.WaitOne(10000)) + { + throw new TimeoutException(); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs @@ -14,15 +14,13 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers /// </summary> internal class TestConfigurationSetProcessor : IConfigurationSetProcessor { - private ConfigurationSet? set; - /// <summary> /// Initializes a new instance of the <see cref="TestConfigurationSetProcessor"/> class. /// </summary> /// <param name="set">The set that this processor is for.</param> internal TestConfigurationSetProcessor(ConfigurationSet? set) { - this.set = set; + this.Set = set; } /// <summary> @@ -44,6 +42,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers new Dictionary<ConfigurationUnit, Exception>(); /// <summary> + /// Gets the ConfigurationSet that this processor targets. + /// </summary> + protected ConfigurationSet? Set { get; private set; } + + /// <summary> /// Creates a new unit processor for the given unit. /// </summary> /// <param name="unit">The unit.</param> @@ -96,6 +99,18 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers } /// <summary> + /// Creates a new test group processor for the given unit. + /// </summary> + /// <param name="unit">The unit.</param> + /// <returns>A new TestConfigurationUnitGroupProcessor for the unit.</returns> + internal TestConfigurationUnitGroupProcessor CreateTestGroupProcessor(ConfigurationUnit unit) + { + TestConfigurationUnitGroupProcessor result = new (unit); + this.Processors[unit] = result; + return result; + } + + /// <summary> /// Creates a new test processor that supports GetAllSettings for the given unit. /// </summary> /// <param name="unit">The unit.</param> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitGroupProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitGroupProcessor.cs @@ -0,0 +1,205 @@ +// ----------------------------------------------------------------------------- +// <copyright file="TestConfigurationUnitGroupProcessor.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.InteropServices.WindowsRuntime; + using System.Threading; + using System.Threading.Tasks; + using Windows.Foundation; + using Windows.Foundation.Collections; + + /// <summary> + /// A test implementation of IConfigurationGroupProcessor. + /// </summary> + internal class TestConfigurationUnitGroupProcessor : TestConfigurationUnitProcessor, IConfigurationGroupProcessor + { + /// <summary> + /// The Setting key that will be used to set the TestResult of the unit. + /// </summary> + internal const string TestResultSetting = "TestResult"; + + /// <summary> + /// The event that is waited on before actually processing the async operations. + /// </summary> + private AutoResetEvent asyncWaitEvent = new AutoResetEvent(false); + + /// <summary> + /// Initializes a new instance of the <see cref="TestConfigurationUnitGroupProcessor"/> class. + /// </summary> + /// <param name="unit">The unit that this processor is for.</param> + internal TestConfigurationUnitGroupProcessor(ConfigurationUnit unit) + : base(unit) + { + } + + /// <summary> + /// Gets the group that this processor targets. + /// </summary> + public object Group + { + get { return this.Unit; } + } + + /// <summary> + /// Gets or sets a value indicating whether the async methods should wait on an event before processing. + /// </summary> + internal bool ShouldWaitOnAsyncEvent { get; set; } = false; + + /// <summary> + /// Apply settings for the group. + /// </summary> + /// <returns>The operation to apply settings.</returns> + public IAsyncOperationWithProgress<IApplyGroupSettingsResult, IApplyGroupMemberSettingsResult> ApplyGroupSettingsAsync() + { + return AsyncInfo.Run((CancellationToken cancellationToken, IProgress<IApplyGroupMemberSettingsResult> progress) => Task.Run<IApplyGroupSettingsResult>(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + ApplyGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List<IApplyGroupMemberSettingsResult>(); + + ApplyGroupSettings(this.Unit.Units, progress, result); + + return result; + })); + } + + /// <summary> + /// Test settings for the group. + /// </summary> + /// <returns>The operation to test settings.</returns> + public IAsyncOperationWithProgress<ITestGroupSettingsResult, ITestSettingsResult> TestGroupSettingsAsync() + { + return AsyncInfo.Run((CancellationToken cancellationToken, IProgress<ITestSettingsResult> progress) => Task.Run<ITestGroupSettingsResult>(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + TestGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List<ITestSettingsResult>(); + + result.TestResult = GetTestResult(this.Unit.Metadata); + TestGroupSettings(this.Unit.Units, progress, result); + + return result; + })); + } + + /// <summary> + /// Gets the tests result for the given unit. + /// </summary> + /// <param name="unit">The unit.</param> + /// <returns>The test result for the unit.</returns> + internal static ConfigurationTestResult GetTestResult(ConfigurationUnit unit) + { + return GetTestResult(unit.Settings); + } + + /// <summary> + /// Gets the tests result for the given values. + /// </summary> + /// <param name="values">The values.</param> + /// <returns>The test result for the values.</returns> + internal static ConfigurationTestResult GetTestResult(ValueSet values) + { + if (values.ContainsKey(TestResultSetting)) + { + string? valueString = values[TestResultSetting]?.ToString(); + if (valueString != null) + { + return Enum.Parse<ConfigurationTestResult>(valueString); + } + } + + return ConfigurationTestResult.Positive; + } + + /// <summary> + /// Applies group settings for the given group members. + /// </summary> + /// <param name="groupMembers">The group members.</param> + /// <param name="progress">The progress reporting object.</param> + /// <param name="result">The result object.</param> + internal static void ApplyGroupSettings(IList<ConfigurationUnit>? groupMembers, IProgress<IApplyGroupMemberSettingsResult> progress, ApplyGroupSettingsResultInstance result) + { + if (groupMembers != null) + { + foreach (ConfigurationUnit unit in groupMembers) + { + ApplyGroupMemberSettingsResultInstance unitResult = new (unit); + + unitResult.State = ConfigurationUnitState.InProgress; + progress.Report(unitResult); + + unitResult.PreviouslyInDesiredState = GetTestResult(unit) == ConfigurationTestResult.Positive; + + if (unit.IsGroup) + { + ApplyGroupSettings(unit.Units, progress, result); + } + + unitResult.State = ConfigurationUnitState.Completed; + progress.Report(unitResult); + + result.UnitResults!.Add(unitResult); + } + } + } + + /// <summary> + /// Tests group settings for the given group members. + /// </summary> + /// <param name="groupMembers">The group members.</param> + /// <param name="progress">The progress reporting object.</param> + /// <param name="result">The result object.</param> + internal static void TestGroupSettings(IList<ConfigurationUnit>? groupMembers, IProgress<ITestSettingsResult> progress, TestGroupSettingsResultInstance result) + { + if (groupMembers != null) + { + foreach (ConfigurationUnit unit in groupMembers) + { + TestSettingsResultInstance unitResult = new (unit); + + if (unit.IsGroup) + { + TestGroupSettings(unit.Units, progress, result); + } + + unitResult.TestResult = GetTestResult(unit); + progress.Report(unitResult); + + result.UnitResults!.Add(unitResult); + } + } + } + + /// <summary> + /// Signals the async event. + /// </summary> + internal void SignalAsyncEvent() + { + this.asyncWaitEvent.Set(); + } + + /// <summary> + /// Waits on the async event. + /// </summary> + private void WaitOnAsyncEvent(CancellationToken cancellationToken) + { + if (this.ShouldWaitOnAsyncEvent) + { + cancellationToken.Register(() => this.asyncWaitEvent.Set()); + if (!this.asyncWaitEvent.WaitOne(10000)) + { + throw new TimeoutException(); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGroupSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGroupSettingsResultInstance.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------------- +// <copyright file="TestGroupSettingsResultInstance.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + + /// <summary> + /// Implements ITestGroupSettingsResult. + /// </summary> + internal class TestGroupSettingsResultInstance : ITestGroupSettingsResult + { + /// <summary> + /// Initializes a new instance of the <see cref="TestGroupSettingsResultInstance"/> class. + /// </summary> + /// <param name="group">The group for this result.</param> + internal TestGroupSettingsResultInstance(object? group) + { + this.Group = group; + } + + /// <inheritdoc/> + public object? Group { get; private init; } + + /// <inheritdoc/> + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// <summary> + /// Gets the implementation object for ResultInformation. + /// </summary> + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// <inheritdoc/> + public ConfigurationTestResult TestResult { get; internal set; } + + /// <inheritdoc/> + public IList<ITestSettingsResult>? UnitResults { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs @@ -0,0 +1,647 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigurationProcessorGroupTests.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// <summary> + /// Unit tests for running group processing. + /// </summary> + [Collection("UnitTestCollection")] + public class ConfigurationProcessorGroupTests : ConfigurationProcessorTestBase + { + /// <summary> + /// Initializes a new instance of the <see cref="ConfigurationProcessorGroupTests"/> class. + /// </summary> + /// <param name="fixture">Unit test fixture.</param> + /// <param name="log">Log helper.</param> + public ConfigurationProcessorGroupTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// <summary> + /// Test a set that was parsed with schema 0.3. + /// </summary> + [Fact] + public void TestSet_Parsed_0_3() + { + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: Name + type: Module/Resource + properties: + c: 3 + d: '4' + - name: Name2 + type: Module/Resource2 + properties: + l: '10' +")); + + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + Assert.Equal(string.Empty, openResult.Field); + Assert.Equal(string.Empty, openResult.Value); + Assert.Equal(0U, openResult.Line); + Assert.Equal(0U, openResult.Column); + + ConfigurationSet configurationSet = openResult.Set; + int unitCount = configurationSet.Units.Count; + + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + + TestConfigurationUnitProcessor[] unitProcessors = new TestConfigurationUnitProcessor[unitCount]; + for (int i = 0; i < unitCount; ++i) + { + unitProcessors[i] = setProcessor.CreateTestProcessor(configurationSet.Units[i]); + if (i == 0) + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Negative }; + } + else + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Positive }; + } + } + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Negative, result.TestResult); + Assert.Equal(unitCount, result.UnitResults.Count); + + for (int i = 0; i < unitCount; ++i) + { + var unitResult = result.UnitResults[i]; + Assert.NotNull(unitResult); + + if (i == 0) + { + Assert.Equal(ConfigurationTestResult.Negative, unitResult.TestResult); + } + else + { + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + } + + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the set processor is a group processor. + /// </summary> + [Fact] + public void TestSet_SetGroupProcessor() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + ConfigurationUnit configurationUnitNegative = this.ConfigurationUnit(); + configurationUnitNegative.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + ConfigurationUnit configurationUnitPositive = this.ConfigurationUnit(); + configurationUnitPositive.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Positive.ToString(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitNegative, configurationUnitPositive }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); + setProcessor.ShouldWaitOnAsyncEvent = true; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + List<TestConfigurationUnitResult> progressValues = new List<TestConfigurationUnitResult>(); + + var operation = processor.TestSetAsync(configurationSet); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress<TestConfigurationSetResult, TestConfigurationUnitResult> op, TestConfigurationUnitResult unitResult) => { progressValues.Add(unitResult); }; + setProcessor.SignalAsyncEvent(); + + operation.AsTask().Wait(); + TestConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Negative, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + Assert.Equal(2, progressValues.Count); + + TestConfigurationUnitResult negativeResult = result.UnitResults.First(x => x.Unit == configurationUnitNegative); + TestConfigurationUnitResult negativeProgress = progressValues.First(x => x.Unit == configurationUnitNegative); + + foreach (TestConfigurationUnitResult unitResult in new TestConfigurationUnitResult[] { negativeResult, negativeProgress }) + { + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Negative, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + TestConfigurationUnitResult positiveResult = result.UnitResults.First(x => x.Unit == configurationUnitPositive); + TestConfigurationUnitResult positiveProgress = progressValues.First(x => x.Unit == configurationUnitPositive); + + foreach (TestConfigurationUnitResult unitResult in new TestConfigurationUnitResult[] { positiveResult, positiveProgress }) + { + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the set processor is a group processor and contains a unit that is also a group. + /// </summary> + [Fact] + public void TestSet_SetGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); + setProcessor.ShouldWaitOnAsyncEvent = true; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + List<TestConfigurationUnitResult> progressValues = new List<TestConfigurationUnitResult>(); + + var operation = processor.TestSetAsync(configurationSet); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress<TestConfigurationSetResult, TestConfigurationUnitResult> op, TestConfigurationUnitResult unitResult) => { progressValues.Add(unitResult); }; + setProcessor.SignalAsyncEvent(); + + operation.AsTask().Wait(); + TestConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + Assert.Equal(3, progressValues.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + foreach (IReadOnlyList<TestConfigurationUnitResult> unitResults in new IReadOnlyList<TestConfigurationUnitResult>[] { result.UnitResults, progressValues }) + { + TestConfigurationUnitResult unitResult = unitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the standard set processor is used and there is a group unit. + /// </summary> + [Fact] + public void TestSet_UnitGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the standard set processor is used and there is a non-group unit that still exposes a group processor. + /// </summary> + [Fact] + public void TestSet_UnitGroupProcessor_WithNonGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }) + { + TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Apply a set that was parsed with schema 0.3. + /// </summary> + [Fact] + public void ApplySet_Parsed_0_3() + { + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: Name + type: Module/Resource + properties: + c: 3 + d: '4' + - name: Name2 + type: Module/Resource2 + properties: + l: '10' +")); + + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + Assert.Equal(string.Empty, openResult.Field); + Assert.Equal(string.Empty, openResult.Value); + Assert.Equal(0U, openResult.Line); + Assert.Equal(0U, openResult.Column); + + ConfigurationSet configurationSet = openResult.Set; + int unitCount = configurationSet.Units.Count; + + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + + TestConfigurationUnitProcessor[] unitProcessors = new TestConfigurationUnitProcessor[unitCount]; + for (int i = 0; i < unitCount; ++i) + { + unitProcessors[i] = setProcessor.CreateTestProcessor(configurationSet.Units[i]); + if (i == 0) + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Negative }; + } + else + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Positive }; + } + } + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.Equal(unitCount, result.UnitResults.Count); + + for (int i = 0; i < unitCount; ++i) + { + var unitResult = result.UnitResults[i]; + Assert.NotNull(unitResult); + + if (i == 0) + { + Assert.False(unitResult.PreviouslyInDesiredState); + } + else + { + Assert.True(unitResult.PreviouslyInDesiredState); + } + + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + for (int i = 0; i < unitCount; ++i) + { + Assert.Equal(1, unitProcessors[i].TestSettingsCalls); + Assert.Equal(0, unitProcessors[i].GetSettingsCalls); + if (i == 0) + { + Assert.Equal(1, unitProcessors[i].ApplySettingsCalls); + } + else + { + Assert.Equal(0, unitProcessors[i].ApplySettingsCalls); + } + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the set processor is a group processor. + /// </summary> + [Fact] + public void ApplySet_SetGroupProcessor() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + + ConfigurationUnit configurationUnitNegative = this.ConfigurationUnit(); + configurationUnitNegative.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + ConfigurationUnit configurationUnitPositive = this.ConfigurationUnit(); + configurationUnitPositive.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Positive.ToString(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitNegative, configurationUnitPositive }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); + setProcessor.ShouldWaitOnAsyncEvent = true; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + List<ConfigurationSetChangeData> progressValues = new List<ConfigurationSetChangeData>(); + + var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> op, ConfigurationSetChangeData unitResult) => { progressValues.Add(unitResult); }; + setProcessor.SignalAsyncEvent(); + + operation.AsTask().Wait(); + ApplyConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(configurationSet.Units.Count, result.UnitResults.Count); + Assert.True((configurationSet.Units.Count * 2) + 2 >= progressValues.Count); + + ApplyConfigurationUnitResult negativeResult = result.UnitResults.First(x => x.Unit == configurationUnitNegative); + + Assert.NotNull(negativeResult); + Assert.NotNull(negativeResult.ResultInformation); + Assert.Null(negativeResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, negativeResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, negativeResult.State); + Assert.False(negativeResult.PreviouslyInDesiredState); + + IEnumerable<ConfigurationSetChangeData> negativeProgress = progressValues.Where(x => x.Unit == configurationUnitNegative); + Assert.Equal(2, negativeProgress.Count()); + + foreach (ConfigurationSetChangeData change in negativeProgress) + { + Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); + Assert.Equal(ConfigurationSetState.InProgress, change.SetState); + Assert.NotNull(change.ResultInformation); + Assert.Null(change.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); + } + + Assert.Single(negativeProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress)); + Assert.Single(negativeProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed)); + + ApplyConfigurationUnitResult positiveResult = result.UnitResults.First(x => x.Unit == configurationUnitPositive); + + Assert.NotNull(positiveResult); + Assert.NotNull(positiveResult.ResultInformation); + Assert.Null(positiveResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, positiveResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, positiveResult.State); + Assert.True(positiveResult.PreviouslyInDesiredState); + + IEnumerable<ConfigurationSetChangeData> positiveProgress = progressValues.Where(x => x.Unit == configurationUnitPositive); + Assert.Equal(2, positiveProgress.Count()); + + foreach (ConfigurationSetChangeData change in positiveProgress) + { + Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); + Assert.Equal(ConfigurationSetState.InProgress, change.SetState); + Assert.NotNull(change.ResultInformation); + Assert.Null(change.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); + } + + Assert.Single(positiveProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress)); + Assert.Single(positiveProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed)); + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the set processor is a group processor and contains a unit that is also a group. + /// </summary> + [Fact] + public void ApplySet_SetGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); + setProcessor.ShouldWaitOnAsyncEvent = true; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + List<ConfigurationSetChangeData> progressValues = new List<ConfigurationSetChangeData>(); + + var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> op, ConfigurationSetChangeData unitResult) => { progressValues.Add(unitResult); }; + setProcessor.SignalAsyncEvent(); + + operation.AsTask().Wait(); + ApplyConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + Assert.True(progressValues.Count <= 2 + (3 * 2)); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); + Assert.True(unitResult.PreviouslyInDesiredState); + + IEnumerable<ConfigurationSetChangeData> unitProgress = progressValues.Where(x => x.Unit == unit); + Assert.True(unitProgress.Count() <= 2); + + foreach (ConfigurationSetChangeData change in unitProgress) + { + Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); + Assert.Equal(ConfigurationSetState.InProgress, change.SetState); + Assert.NotNull(change.ResultInformation); + Assert.Null(change.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); + } + + Assert.True(unitProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress).Count() <= 1); + Assert.True(unitProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed).Count() <= 1); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the standard set processor is used and there is a group unit. + /// </summary> + [Fact] + public void ApplySet_UnitGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); + Assert.True(unitResult.PreviouslyInDesiredState); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// <summary> + /// Test when the standard set processor is used and there is a non-group unit that still exposes a group processor. + /// </summary> + [Fact] + public void ApplySet_UnitGroupProcessor_WithNonGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }) + { + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); + Assert.True(unitResult.PreviouslyInDesiredState); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + } +} diff --git a/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.cpp b/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.cpp @@ -7,7 +7,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { ApplyConfigurationSetResult::ApplyConfigurationSetResult() : - m_unitResults(single_threaded_vector<ApplyConfigurationUnitResult>()) + m_unitResults(multi_threaded_vector<ApplyConfigurationUnitResult>()) {} Windows::Foundation::Collections::IVectorView<ApplyConfigurationUnitResult> ApplyConfigurationSetResult::UnitResults() const diff --git a/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.cpp b/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.cpp @@ -3,10 +3,31 @@ #include "pch.h" #include "ApplyConfigurationUnitResult.h" #include "ApplyConfigurationUnitResult.g.cpp" -#include "ConfigurationUnitResultInformation.h" namespace winrt::Microsoft::Management::Configuration::implementation { + ApplyConfigurationUnitResult::ApplyConfigurationUnitResult() : + m_resultInformation(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()) + { + } + + void ApplyConfigurationUnitResult::Initialize(const IApplySettingsResult& result) + { + m_unit = result.Unit(); + THROW_HR_IF(E_POINTER, !m_unit); + m_resultInformation->Initialize(result.ResultInformation()); + } + + void ApplyConfigurationUnitResult::Initialize(const IApplyGroupMemberSettingsResult& unitResult) + { + m_unit = unitResult.Unit(); + THROW_HR_IF(E_POINTER, !m_unit); + m_resultInformation->Initialize(unitResult.ResultInformation()); + m_state = unitResult.State(); + m_previouslyInDesiredState = unitResult.PreviouslyInDesiredState(); + m_rebootRequired = unitResult.RebootRequired(); + } + ConfigurationUnit ApplyConfigurationUnitResult::Unit() { return m_unit; @@ -49,11 +70,16 @@ namespace winrt::Microsoft::Management::Configuration::implementation IConfigurationUnitResultInformation ApplyConfigurationUnitResult::ResultInformation() { - return m_resultInformation; + return *m_resultInformation; } - void ApplyConfigurationUnitResult::ResultInformation(IConfigurationUnitResultInformation value) + void ApplyConfigurationUnitResult::ResultInformation(const Configuration::IConfigurationUnitResultInformation& value) { - m_resultInformation = std::move(value); + m_resultInformation->Initialize(value); + } + + ApplyConfigurationUnitResult::ResultInformationT ApplyConfigurationUnitResult::ResultInformationInternal() + { + return m_resultInformation; } } diff --git a/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.h b/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.h @@ -2,22 +2,28 @@ // Licensed under the MIT License. #pragma once #include "ApplyConfigurationUnitResult.g.h" +#include "ConfigurationUnitResultInformation.h" #include <atomic> namespace winrt::Microsoft::Management::Configuration::implementation { - struct ApplyConfigurationUnitResult : ApplyConfigurationUnitResultT<ApplyConfigurationUnitResult> + struct ApplyConfigurationUnitResult : ApplyConfigurationUnitResultT<ApplyConfigurationUnitResult, IApplyGroupMemberSettingsResult> { using ConfigurationUnit = Configuration::ConfigurationUnit; + using ResultInformationT = decltype(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()); - ApplyConfigurationUnitResult() = default; + ApplyConfigurationUnitResult(); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(const IApplySettingsResult& result); + void Initialize(const IApplyGroupMemberSettingsResult& unitResult); + void Unit(ConfigurationUnit value); void State(ConfigurationUnitState value); void PreviouslyInDesiredState(bool value); void RebootRequired(bool value); - void ResultInformation(IConfigurationUnitResultInformation value); + void ResultInformation(const Configuration::IConfigurationUnitResultInformation& value); + ResultInformationT ResultInformationInternal(); #endif ConfigurationUnit Unit(); @@ -32,7 +38,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::atomic<ConfigurationUnitState> m_state = ConfigurationUnitState::Pending; bool m_previouslyInDesiredState = false; bool m_rebootRequired = false; - IConfigurationUnitResultInformation m_resultInformation; + ResultInformationT m_resultInformation; #endif }; } diff --git a/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.cpp b/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.cpp @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ApplyGroupSettingsResult.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + ApplyGroupSettingsResult::ApplyGroupSettingsResult() : + m_resultInformation(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()), + m_unitResults(winrt::multi_threaded_vector<IApplyGroupMemberSettingsResult>()) + {} + + void ApplyGroupSettingsResult::Group(const Windows::Foundation::IInspectable& value) + { + m_group = value; + } + + void ApplyGroupSettingsResult::RebootRequired(bool value) + { + m_rebootRequired = value; + } + + ApplyGroupSettingsResult::ResultInformationPtr ApplyGroupSettingsResult::ResultInformationInternal() + { + return m_resultInformation; + } + + Windows::Foundation::IInspectable ApplyGroupSettingsResult::Group() + { + return m_group; + } + + bool ApplyGroupSettingsResult::RebootRequired() + { + return m_rebootRequired; + } + + IConfigurationUnitResultInformation ApplyGroupSettingsResult::ResultInformation() + { + return *m_resultInformation; + } + + Windows::Foundation::Collections::IVector<IApplyGroupMemberSettingsResult> ApplyGroupSettingsResult::UnitResults() + { + return m_unitResults; + } +} diff --git a/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.h b/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.h @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ApplyGroupSettingsResult : winrt::implements<ApplyGroupSettingsResult, IApplyGroupSettingsResult> + { + ApplyGroupSettingsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + using ResultInformationPtr = decltype(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()); + + void Group(const Windows::Foundation::IInspectable& value); + void RebootRequired(bool value); + ResultInformationPtr ResultInformationInternal(); +#endif + + Windows::Foundation::IInspectable Group(); + bool RebootRequired(); + IConfigurationUnitResultInformation ResultInformation(); + Windows::Foundation::Collections::IVector<IApplyGroupMemberSettingsResult> UnitResults(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::IInspectable m_group; + bool m_rebootRequired = false; + ResultInformationPtr m_resultInformation; + Windows::Foundation::Collections::IVector<IApplyGroupMemberSettingsResult> m_unitResults; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationParameter.cpp b/src/Microsoft.Management.Configuration/ConfigurationParameter.cpp @@ -97,7 +97,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationParameter::AllowedValues(std::vector<Windows::Foundation::IInspectable>&& value) { - m_allowedValues = winrt::single_threaded_vector<Windows::Foundation::IInspectable>(std::move(value)); + m_allowedValues = winrt::multi_threaded_vector<Windows::Foundation::IInspectable>(std::move(value)); } uint32_t ConfigurationParameter::MinimumLength() diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp @@ -8,7 +8,7 @@ #include "ConfigurationSetParser.h" #include "DiagnosticInformationInstance.h" #include "ApplyConfigurationSetResult.h" -#include "ConfigurationSetApplyProcessor.h" +#include "ApplyConfigurationUnitResult.h" #include "TestConfigurationSetResult.h" #include "TestConfigurationUnitResult.h" #include "ConfigurationUnitResultInformation.h" @@ -18,6 +18,7 @@ #include "ConfigurationSetChangeData.h" #include "GetConfigurationUnitDetailsResult.h" #include "GetConfigurationSetDetailsResult.h" +#include "DefaultSetGroupProcessor.h" #include <AppInstallerErrors.h> #include <AppInstallerStrings.h> @@ -107,12 +108,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation static AttachWilFailureCallback s_callbackAttach; } }; - - // Specifies the set of intents that should execute during a Test request - bool ShouldTestDuringTest(ConfigurationUnitIntent intent) - { - return (intent == ConfigurationUnitIntent::Assert || intent == ConfigurationUnitIntent::Apply); - } } ConfigurationProcessor::ConfigurationProcessor() @@ -450,18 +445,125 @@ namespace winrt::Microsoft::Management::Configuration::implementation { auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - bool consistencyCheckOnly = WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly); - IConfigurationSetProcessor setProcessor; + IConfigurationGroupProcessor groupProcessor; - if (!consistencyCheckOnly) + if (WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) + { + // If performing a consistency check, always use the default processor and let it know as well + auto defaultGroupProcessor = make_self<wil::details::module_count_wrapper<implementation::DefaultSetGroupProcessor>>(); + defaultGroupProcessor->Initialize(configurationSet, nullptr, m_threadGlobals, true); + groupProcessor = *defaultGroupProcessor; + } + else { - setProcessor = m_factory.CreateSetProcessor(configurationSet); + groupProcessor = GetSetGroupProcessor(configurationSet); } - ConfigurationSetApplyProcessor applyProcessor{ configurationSet, m_threadGlobals.GetTelemetryLogger(), std::move(setProcessor), std::move(progress) }; - applyProcessor.Process(consistencyCheckOnly); + auto result = make_self<wil::details::module_count_wrapper<implementation::ApplyConfigurationSetResult>>(); + + // Build out the unit results and a map to find them quickly + using UnitResultType = decltype(make_self<wil::details::module_count_wrapper<implementation::ApplyConfigurationUnitResult>>()); + std::map<guid, UnitResultType> unitResultMap; + + std::function<void(const winrt::Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>&)> createUnitResults = + [&](const winrt::Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit>& units) + { + for (const Configuration::ConfigurationUnit& unit : units) + { + // Add to result + UnitResultType applyUnitResult = make_self<wil::details::module_count_wrapper<implementation::ApplyConfigurationUnitResult>>(); + applyUnitResult->Unit(unit); + result->UnitResultsVector().Append(*applyUnitResult); + + // Add to map + unitResultMap.emplace(unit.InstanceIdentifier(), applyUnitResult); + + // Handle members if present + if (unit.IsGroup()) + { + createUnitResults(unit.Units()); + } + } + }; + + createUnitResults(configurationSet.Units()); + + progress.Result(*result); + + try + { + // TODO: Send pending when blocked by another configuration run + try + { + progress.Progress(implementation::ConfigurationSetChangeData::Create(ConfigurationSetState::InProgress)); + } + CATCH_LOG(); + + auto applyOperation = groupProcessor.ApplyGroupSettingsAsync(); + + // Forward unit result progress to caller + applyOperation.Progress([&](const auto&, const IApplyGroupMemberSettingsResult& unitResult) + { + // Update overall result + auto itr = unitResultMap.find(unitResult.Unit().InstanceIdentifier()); + if (itr != unitResultMap.end()) + { + itr->second->Initialize(unitResult); + } + + // Create progress object + auto applyResult = make_self<wil::details::module_count_wrapper<implementation::ConfigurationSetChangeData>>(); + applyResult->Initialize(unitResult); + progress.Progress(*applyResult); + }); + + // Cancel the inner operation if we are cancelled + progress.Callback([applyOperation]() { applyOperation.Cancel(); }); + + IApplyGroupSettingsResult applyResult = applyOperation.get(); + + // Place all results from the processor into our result + if (applyResult.ResultInformation()) + { + result->ResultCode(applyResult.ResultInformation().ResultCode()); + } + + for (const IApplyGroupMemberSettingsResult& unitResult : applyResult.UnitResults()) + { + // Update overall result + auto itr = unitResultMap.find(unitResult.Unit().InstanceIdentifier()); + if (itr == unitResultMap.end()) + { + continue; + } + + itr->second->Initialize(unitResult); + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate( + configurationSet.InstanceIdentifier(), + itr->second->Unit(), + ConfigurationUnitIntent::Apply, + TelemetryTraceLogger::ApplyAction, + itr->second->ResultInformation()); + } + + try + { + progress.Progress(implementation::ConfigurationSetChangeData::Create(ConfigurationSetState::Completed)); + } + CATCH_LOG(); - return applyProcessor.Result(); + m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForApply(*winrt::get_self<implementation::ConfigurationSet>(configurationSet), *result); + return *result; + } + catch (...) + { + m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForApplyException( + *winrt::get_self<implementation::ConfigurationSet>(configurationSet), + LOG_CAUGHT_EXCEPTION(), + *result); + throw; + } } Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSet(const ConfigurationSet& configurationSet) @@ -488,73 +590,42 @@ namespace winrt::Microsoft::Management::Configuration::implementation { auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); + IConfigurationGroupProcessor groupProcessor = GetSetGroupProcessor(configurationSet); auto result = make_self<wil::details::module_count_wrapper<implementation::TestConfigurationSetResult>>(); result->TestResult(ConfigurationTestResult::NotRun); progress.Result(*result); try { - for (const auto& unit : configurationSet.Units()) - { - AICLI_LOG(Config, Info, << "Testing configuration unit: " << AppInstaller::Utility::ConvertToUTF8(unit.Type())); + auto testOperation = groupProcessor.TestGroupSettingsAsync(); - auto testResult = make_self<wil::details::module_count_wrapper<implementation::TestConfigurationUnitResult>>(); - auto unitResult = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>(); - testResult->Initialize(unit, *unitResult); - - if (ShouldTestDuringTest(unit.Intent())) + // Forward unit result progress to caller + testOperation.Progress([&](const auto&, const ITestSettingsResult& unitResult) { - progress.ThrowIfCancelled(); - - IConfigurationUnitProcessor unitProcessor; - - try - { - // TODO: Directives overlay to prevent running elevated for test - unitProcessor = setProcessor.CreateUnitProcessor(unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } + auto testResult = make_self<wil::details::module_count_wrapper<implementation::TestConfigurationUnitResult>>(); + testResult->Initialize(unitResult); + progress.Progress(*testResult); + }); - progress.ThrowIfCancelled(); + // Cancel the inner operation if we are cancelled + progress.Callback([testOperation]() { testOperation.Cancel(); }); - if (unitProcessor) - { - try - { - ITestSettingsResult settingsResult = unitProcessor.TestSettings(); - testResult->TestResult(settingsResult.TestResult()); - testResult->ResultInformation(settingsResult.ResultInformation()); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate( - configurationSet.InstanceIdentifier(), - unit, - ConfigurationUnitIntent::Assert, - TelemetryTraceLogger::TestAction, - testResult->ResultInformation()); - } - } - else - { - testResult->TestResult(ConfigurationTestResult::NotRun); - } + ITestGroupSettingsResult testResult = testOperation.get(); - if (FAILED(unitResult->ResultCode())) - { - testResult->TestResult(ConfigurationTestResult::Failed); - } + // Place all results from the processor into our result + for (const ITestSettingsResult& unitResult : testResult.UnitResults()) + { + auto testUnitResult = make_self<wil::details::module_count_wrapper<implementation::TestConfigurationUnitResult>>(); + testUnitResult->Initialize(unitResult); - result->AppendUnitResult(*testResult); + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate( + configurationSet.InstanceIdentifier(), + testUnitResult->Unit(), + ConfigurationUnitIntent::Assert, + TelemetryTraceLogger::TestAction, + testUnitResult->ResultInformation()); - progress.Progress(*testResult); + result->AppendUnitResult(*testUnitResult); } m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForTest(*winrt::get_self<implementation::ConfigurationSet>(configurationSet), *result); @@ -605,7 +676,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation try { - // TODO: Directives overlay to prevent running elevated for get unitProcessor = setProcessor.CreateUnitProcessor(unit); } catch (...) @@ -633,7 +703,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation return *result; } - + Configuration::GetAllConfigurationUnitSettingsResult ConfigurationProcessor::GetAllUnitSettings(const ConfigurationUnit& unit) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); @@ -706,6 +776,21 @@ namespace winrt::Microsoft::Management::Configuration::implementation return *result; } + IConfigurationGroupProcessor ConfigurationProcessor::GetSetGroupProcessor(const ConfigurationSet& configurationSet) + { + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); + + IConfigurationGroupProcessor result = setProcessor.try_as<IConfigurationGroupProcessor>(); + if (!result) + { + auto groupProcessor = make_self<wil::details::module_count_wrapper<implementation::DefaultSetGroupProcessor>>(); + groupProcessor->Initialize(configurationSet, setProcessor, m_threadGlobals); + result = *groupProcessor; + } + + return result; + } + HRESULT STDMETHODCALLTYPE ConfigurationProcessor::SetLifetimeWatcher(IUnknown* watcher) { return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationProcessor.h @@ -115,9 +115,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation AppInstaller::WinRT::AsyncProgress<TestConfigurationSetResult, TestConfigurationUnitResult> progress = {}); GetConfigurationUnitSettingsResult GetUnitSettingsImpl(const ConfigurationUnit& unit, AppInstaller::WinRT::AsyncCancellation cancellation = {}); - + GetAllConfigurationUnitSettingsResult GetAllUnitSettingsImpl(const ConfigurationUnit& unit, AppInstaller::WinRT::AsyncCancellation cancellation = {}); + IConfigurationGroupProcessor GetSetGroupProcessor(const ConfigurationSet& configurationSet); + void SendDiagnosticsImpl(const IDiagnosticInformation& information); IConfigurationSetProcessorFactory m_factory = nullptr; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.cpp b/src/Microsoft.Management.Configuration/ConfigurationSet.cpp @@ -22,12 +22,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSet::Units(std::vector<Configuration::ConfigurationUnit>&& units) { - m_units = winrt::single_threaded_vector<Configuration::ConfigurationUnit>(std::move(units)); + m_units = winrt::multi_threaded_vector<Configuration::ConfigurationUnit>(std::move(units)); } void ConfigurationSet::Parameters(std::vector<Configuration::ConfigurationParameter>&& value) { - m_parameters = winrt::single_threaded_vector<Configuration::ConfigurationParameter>(std::move(value)); + m_parameters = winrt::multi_threaded_vector<Configuration::ConfigurationParameter>(std::move(value)); } bool ConfigurationSet::IsFromHistory() const diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.h b/src/Microsoft.Management.Configuration/ConfigurationSet.h @@ -74,11 +74,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation hstring m_path; guid m_instanceIdentifier; clock::time_point m_firstApply{}; - Windows::Foundation::Collections::IVector<ConfigurationUnit> m_units{ winrt::single_threaded_vector<ConfigurationUnit>() }; + Windows::Foundation::Collections::IVector<ConfigurationUnit> m_units{ winrt::multi_threaded_vector<ConfigurationUnit>() }; hstring m_schemaVersion; winrt::event<Windows::Foundation::TypedEventHandler<WinRT_Self, ConfigurationSetChangeData>> m_configurationSetChange; Windows::Foundation::Collections::ValueSet m_metadata; - Windows::Foundation::Collections::IVector<ConfigurationParameter> m_parameters{ winrt::single_threaded_vector<ConfigurationParameter>() }; + Windows::Foundation::Collections::IVector<ConfigurationParameter> m_parameters{ winrt::multi_threaded_vector<ConfigurationParameter>() }; Windows::Foundation::Collections::ValueSet m_variables; Windows::Foundation::Uri m_schemaUri = nullptr; #endif diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp @@ -18,17 +18,30 @@ namespace winrt::Microsoft::Management::Configuration::implementation using namespace AppInstaller::Utility; return FoldCase(NormalizedString{ identifier }); } + + bool AssertFilter(ConfigurationUnitIntent intent) + { + return intent == ConfigurationUnitIntent::Assert; + } + + bool InformFilter(ConfigurationUnitIntent intent) + { + return intent == ConfigurationUnitIntent::Inform; + } + + bool ApplyFilter(ConfigurationUnitIntent intent) + { + return intent == ConfigurationUnitIntent::Apply || intent == ConfigurationUnitIntent::Unknown; + } } ConfigurationSetApplyProcessor::ConfigurationSetApplyProcessor( const Configuration::ConfigurationSet& configurationSet, - const TelemetryTraceLogger& telemetry, - IConfigurationSetProcessor&& setProcessor, - AppInstaller::WinRT::AsyncProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>&& progress) : + IConfigurationSetProcessor setProcessor, + progress_type&& progress) : m_configurationSet(configurationSet), m_setProcessor(std::move(setProcessor)), - m_telemetry(telemetry), - m_result(make_self<wil::details::module_count_wrapper<implementation::ApplyConfigurationSetResult>>()), + m_result(make_self<wil::details::module_count_wrapper<implementation::ApplyGroupSettingsResult>>()), m_progress(std::move(progress)) { // Create a copy of the set of configuration units @@ -40,7 +53,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation for (const auto& unit : unitsToProcess) { m_unitInfo.emplace_back(unit); - m_result->UnitResultsVector().Append(*m_unitInfo.back().Result); + m_result->UnitResults().Append(*m_unitInfo.back().Result); } m_progress.Result(*m_result); @@ -48,56 +61,22 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetApplyProcessor::Process(bool preProcessOnly) { - try - { - if (PreProcess() && !preProcessOnly) - { - // TODO: Send pending when blocked by another configuration run - //SendProgress(ConfigurationSetState::Pending); - - SendProgress(ConfigurationSetState::InProgress); - - ProcessInternal(HasProcessedSuccessfully, &ConfigurationSetApplyProcessor::ProcessUnit, true); - } - - SendProgress(ConfigurationSetState::Completed); - - if (!preProcessOnly) - { - m_telemetry.LogConfigProcessingSummaryForApply(*winrt::get_self<implementation::ConfigurationSet>(m_configurationSet), *m_result); - } - } - catch (...) + if (PreProcess() && !preProcessOnly) { - if (!preProcessOnly) - { - const auto& configurationSet = *winrt::get_self<implementation::ConfigurationSet>(m_configurationSet); - m_telemetry.LogConfigProcessingSummary( - configurationSet.InstanceIdentifier(), - configurationSet.IsFromHistory(), - ConfigurationUnitIntent::Apply, - LOG_CAUGHT_EXCEPTION(), - ConfigurationUnitResultSource::Internal, - GetProcessingSummaryFor(ConfigurationUnitIntent::Assert), - GetProcessingSummaryFor(ConfigurationUnitIntent::Inform), - GetProcessingSummaryFor(ConfigurationUnitIntent::Apply)); - } - - throw; + ProcessInternal(HasProcessedSuccessfully, &ConfigurationSetApplyProcessor::ProcessUnit, true); } } - Configuration::ApplyConfigurationSetResult ConfigurationSetApplyProcessor::Result() const + IApplyGroupSettingsResult ConfigurationSetApplyProcessor::Result() const { return *m_result; } ConfigurationSetApplyProcessor::UnitInfo::UnitInfo(const Configuration::ConfigurationUnit& unit) : - Unit(unit), Result(make_self<wil::details::module_count_wrapper<implementation::ApplyConfigurationUnitResult>>()), - ResultInformation(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()) + Unit(unit), Result(make_self<wil::details::module_count_wrapper<implementation::ApplyConfigurationUnitResult>>()) { Result->Unit(unit); - Result->ResultInformation(*ResultInformation); + ResultInformation = Result->ResultInformationInternal(); } bool ConfigurationSetApplyProcessor::PreProcess() @@ -115,7 +94,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (!result) { // This is the only error that adding to the map can produce - m_result->ResultCode(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER); + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER); return false; } @@ -151,7 +130,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (!result) { // This is the only error that adding to the map can produce - m_result->ResultCode(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY); + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY); return false; } @@ -160,7 +139,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation // The preprocessing simulates processing as if every unit run was successful. // If it fails, this means that there are unit definitions whose dependencies cannot be satisfied. // The only reason for that is a cycle in the dependency graph somewhere. - m_result->ResultCode(WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE); + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE); return false; } @@ -209,7 +188,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation unitsToProcess, checkDependencyFunction, processUnitFunction, - ConfigurationUnitIntent::Assert, + AssertFilter, WINGET_CONFIG_ERROR_ASSERTION_FAILED, WINGET_CONFIG_ERROR_ASSERTION_FAILED, sendProgress)) @@ -222,7 +201,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation unitsToProcess, checkDependencyFunction, processUnitFunction, - ConfigurationUnitIntent::Inform, + InformFilter, WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, sendProgress)) @@ -235,7 +214,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation unitsToProcess, checkDependencyFunction, processUnitFunction, - ConfigurationUnitIntent::Apply, + ApplyFilter, E_FAIL, // This should not happen as there are no other intents left WINGET_CONFIG_ERROR_SET_APPLY_FAILED, sendProgress); @@ -245,7 +224,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::vector<size_t>& unitsToProcess, CheckDependencyPtr checkDependencyFunction, ProcessUnitPtr processUnitFunction, - ConfigurationUnitIntent intent, + IntentFilterPtr intentFilter, hresult errorForOtherIntents, hresult errorForFailures, bool sendProgress) @@ -259,7 +238,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation for (auto itr = unitsToProcess.begin(), end = unitsToProcess.end(); itr != end; ++itr) { UnitInfo& unitInfo = m_unitInfo[*itr]; - if (HasIntentAndSatisfiedDependencies(unitInfo, intent, checkDependencyFunction)) + if (HasIntentAndSatisfiedDependencies(unitInfo, intentFilter, checkDependencyFunction)) { if (!(this->*processUnitFunction)(unitInfo)) { @@ -277,7 +256,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation for (size_t index : unitsToProcess) { UnitInfo& unitInfo = m_unitInfo[index]; - if (unitInfo.Unit.Intent() == intent) + if (intentFilter(unitInfo.Unit.Intent())) { hasRemainingDependencies = true; unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, ConfigurationUnitResultSource::Precondition); @@ -294,7 +273,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation for (size_t index : unitsToProcess) { UnitInfo& unitInfo = m_unitInfo[index]; - if (unitInfo.Unit.Intent() != intent) + if (!intentFilter(unitInfo.Unit.Intent())) { unitInfo.ResultInformation->Initialize(errorForOtherIntents, ConfigurationUnitResultSource::Precondition); if (sendProgress) @@ -306,11 +285,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (hasFailure) { - m_result->ResultCode(errorForFailures); + m_result->ResultInformationInternal()->ResultCode(errorForFailures); } else // hasRemainingDependencies { - m_result->ResultCode(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED); + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED); } return false; } @@ -320,12 +299,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation bool ConfigurationSetApplyProcessor::HasIntentAndSatisfiedDependencies( const UnitInfo& unitInfo, - ConfigurationUnitIntent intent, + IntentFilterPtr intentFilter, CheckDependencyPtr checkDependencyFunction) const { bool result = false; - if (unitInfo.Unit.Intent() == intent) + if (intentFilter(unitInfo.Unit.Intent())) { result = true; for (size_t dependencyIndex : unitInfo.DependencyIndices) @@ -393,7 +372,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_progress.ThrowIfCancelled(); bool result = false; - std::string_view action; try { @@ -401,7 +379,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation { case ConfigurationUnitIntent::Assert: { - action = TelemetryTraceLogger::TestAction; ITestSettingsResult settingsResult = unitProcessor.TestSettings(); if (settingsResult.TestResult() == ConfigurationTestResult::Positive) @@ -426,7 +403,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation case ConfigurationUnitIntent::Inform: { // Force the processor to retrieve the settings - action = TelemetryTraceLogger::GetAction; IGetSettingsResult settingsResult = unitProcessor.GetSettings(); if (SUCCEEDED(settingsResult.ResultInformation().ResultCode())) { @@ -440,40 +416,78 @@ namespace winrt::Microsoft::Management::Configuration::implementation break; case ConfigurationUnitIntent::Apply: + case ConfigurationUnitIntent::Unknown: { - action = TelemetryTraceLogger::TestAction; - ITestSettingsResult testSettingsResult = unitProcessor.TestSettings(); + // Check for a group processor and let it do the work if present + IConfigurationGroupProcessor groupProcessor = unitProcessor.try_as<IConfigurationGroupProcessor>(); - if (testSettingsResult.TestResult() == ConfigurationTestResult::Positive) + if (groupProcessor) { - unitInfo.Result->PreviouslyInDesiredState(true); - result = true; + auto applyOperation = groupProcessor.ApplyGroupSettingsAsync(); + + applyOperation.Progress([&](const auto&, const IApplyGroupMemberSettingsResult& unitResult) + { + m_progress.Progress(unitResult); + }); + + // Cancel the inner operation if we are cancelled + m_progress.Callback([applyOperation]() { applyOperation.Cancel(); }); + + IApplyGroupSettingsResult groupResult = applyOperation.get(); + + // Put all of the group's unit results in our unit results + bool groupPreviouslyInDesiredState = true; + + for (const auto& groupUnitResult : groupResult.UnitResults()) + { + m_result->UnitResults().Append(groupUnitResult); + groupPreviouslyInDesiredState = groupPreviouslyInDesiredState && groupUnitResult.PreviouslyInDesiredState(); + } + + // Copy the group result into the existing unit result for the group + unitInfo.Result->PreviouslyInDesiredState(groupPreviouslyInDesiredState); + unitInfo.ResultInformation->Initialize(groupResult.ResultInformation()); + + if (SUCCEEDED(unitInfo.ResultInformation->ResultCode())) + { + unitInfo.Result->RebootRequired(groupResult.RebootRequired()); + result = true; + } } - else if (testSettingsResult.TestResult() == ConfigurationTestResult::Negative) + else { - // Just in case testing took a while, check for cancellation before moving on to applying - m_progress.ThrowIfCancelled(); + ITestSettingsResult testSettingsResult = unitProcessor.TestSettings(); - action = TelemetryTraceLogger::ApplyAction; - IApplySettingsResult applySettingsResult = unitProcessor.ApplySettings(); - if (SUCCEEDED(applySettingsResult.ResultInformation().ResultCode())) + if (testSettingsResult.TestResult() == ConfigurationTestResult::Positive) { - unitInfo.Result->RebootRequired(applySettingsResult.RebootRequired()); + unitInfo.Result->PreviouslyInDesiredState(true); result = true; } + else if (testSettingsResult.TestResult() == ConfigurationTestResult::Negative) + { + // Just in case testing took a while, check for cancellation before moving on to applying + m_progress.ThrowIfCancelled(); + + IApplySettingsResult applySettingsResult = unitProcessor.ApplySettings(); + if (SUCCEEDED(applySettingsResult.ResultInformation().ResultCode())) + { + unitInfo.Result->RebootRequired(applySettingsResult.RebootRequired()); + result = true; + } + else + { + unitInfo.ResultInformation->Initialize(applySettingsResult.ResultInformation()); + } + } + else if (testSettingsResult.TestResult() == ConfigurationTestResult::Failed) + { + unitInfo.ResultInformation->Initialize(testSettingsResult.ResultInformation()); + } else { - unitInfo.ResultInformation->Initialize(applySettingsResult.ResultInformation()); + unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); } } - else if (testSettingsResult.TestResult() == ConfigurationTestResult::Failed) - { - unitInfo.ResultInformation->Initialize(testSettingsResult.ResultInformation()); - } - else - { - unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); - } } break; @@ -487,26 +501,16 @@ namespace winrt::Microsoft::Management::Configuration::implementation ExtractUnitResultInformation(std::current_exception(), unitInfo.ResultInformation); } - m_telemetry.LogConfigUnitRunIfAppropriate(m_configurationSet.InstanceIdentifier(), unitInfo.Unit, ConfigurationUnitIntent::Apply, action, *unitInfo.ResultInformation); return result; } - void ConfigurationSetApplyProcessor::SendProgress(ConfigurationSetState state) - { - try - { - m_progress.Progress(implementation::ConfigurationSetChangeData::Create(state)); - } - CATCH_LOG(); - } - void ConfigurationSetApplyProcessor::SendProgress(ConfigurationUnitState state, const UnitInfo& unitInfo) { unitInfo.Result->State(state); try { - m_progress.Progress(implementation::ConfigurationSetChangeData::Create(state, *unitInfo.ResultInformation, unitInfo.Unit)); + m_progress.Progress(*unitInfo.Result); } CATCH_LOG(); } @@ -518,29 +522,4 @@ namespace winrt::Microsoft::Management::Configuration::implementation SendProgress(state, unitInfo); } } - - TelemetryTraceLogger::ProcessingSummaryForIntent ConfigurationSetApplyProcessor::GetProcessingSummaryFor(ConfigurationUnitIntent intent) const - { - TelemetryTraceLogger::ProcessingSummaryForIntent result{ intent, 0, 0, 0 }; - - for (const auto& unitInfo : m_unitInfo) - { - if (unitInfo.Unit.Intent() == intent) - { - ++result.Count; - - if (unitInfo.Processed) - { - ++result.Run; - - if (FAILED(unitInfo.ResultInformation->ResultCode())) - { - ++result.Failed; - } - } - } - } - - return result; - } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h @@ -3,10 +3,9 @@ #pragma once #include "ConfigurationSet.h" #include "ConfigurationUnit.h" -#include "ApplyConfigurationSetResult.h" +#include "ApplyGroupSettingsResult.h" #include "ApplyConfigurationUnitResult.h" #include "ConfigurationUnitResultInformation.h" -#include "Telemetry/Telemetry.h" #include <winget/AsyncTokens.h> #include <map> @@ -18,20 +17,20 @@ namespace winrt::Microsoft::Management::Configuration::implementation // A helper to better organize the configuration set Apply. struct ConfigurationSetApplyProcessor { - using ApplyConfigurationSetResult = Configuration::ApplyConfigurationSetResult; using ConfigurationSet = Configuration::ConfigurationSet; using ConfigurationUnit = Configuration::ConfigurationUnit; using ConfigurationSetChangeData = Configuration::ConfigurationSetChangeData; - using result_type = decltype(make_self<wil::details::module_count_wrapper<implementation::ApplyConfigurationSetResult>>()); + using result_type = decltype(make_self<wil::details::module_count_wrapper<implementation::ApplyGroupSettingsResult>>()); + using progress_type = AppInstaller::WinRT::AsyncProgress<IApplyGroupSettingsResult, IApplyGroupMemberSettingsResult>; - ConfigurationSetApplyProcessor(const ConfigurationSet& configurationSet, const TelemetryTraceLogger& telemetry, IConfigurationSetProcessor&& setProcessor, AppInstaller::WinRT::AsyncProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>&& progress); + ConfigurationSetApplyProcessor(const ConfigurationSet& configurationSet, IConfigurationSetProcessor setProcessor, progress_type&& progress); // Processes the apply for the configuration set. void Process(bool preProcessOnly = false); // Gets the result object. - ApplyConfigurationSetResult Result() const; + IApplyGroupSettingsResult Result() const; private: // Contains all of the relevant data for a configuration unit. @@ -59,6 +58,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Processes the unit; returns true if successful, false if not. using ProcessUnitPtr = bool (ConfigurationSetApplyProcessor::*)(UnitInfo&); + // Return true to process these intents, false to skip them. + using IntentFilterPtr = bool (*)(ConfigurationUnitIntent); + // Runs the processing using the given functions. bool ProcessInternal(CheckDependencyPtr checkDependencyFunction, ProcessUnitPtr processUnitFunction, bool sendProgress = false); @@ -67,7 +69,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::vector<size_t>& unitsToProcess, CheckDependencyPtr checkDependencyFunction, ProcessUnitPtr processUnitFunction, - ConfigurationUnitIntent intent, + IntentFilterPtr intentFilter, hresult errorForOtherIntents, hresult errorForFailures, bool sendProgress); @@ -75,7 +77,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Determines if the given unit has the given intent and all of its dependencies are satisfied bool HasIntentAndSatisfiedDependencies( const UnitInfo& unitInfo, - ConfigurationUnitIntent intent, + IntentFilterPtr intentFilter, CheckDependencyPtr checkDependencyFunction) const; // Checks a dependency for preprocessing. @@ -92,18 +94,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Sends progress // TODO: Eventually these functions/call sites will be used for history - void SendProgress(ConfigurationSetState state); void SendProgress(ConfigurationUnitState state, const UnitInfo& unitInfo); void SendProgressIfNotComplete(ConfigurationUnitState state, const UnitInfo& unitInfo); - // For exception telemetry, get our internal status - TelemetryTraceLogger::ProcessingSummaryForIntent GetProcessingSummaryFor(ConfigurationUnitIntent intent) const; - ConfigurationSet m_configurationSet; IConfigurationSetProcessor m_setProcessor; - const TelemetryTraceLogger& m_telemetry; result_type m_result; - AppInstaller::WinRT::AsyncProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> m_progress; + progress_type m_progress; std::vector<UnitInfo> m_unitInfo; std::map<std::string, size_t> m_idToUnitInfoIndex; hresult m_resultCode; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp @@ -35,6 +35,15 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_unit = unit; } + void ConfigurationSetChangeData::Initialize(const IApplyGroupMemberSettingsResult& unitResult) + { + m_change = ConfigurationSetChangeEventType::UnitStateChanged; + m_setState = ConfigurationSetState::InProgress; + m_unitState = unitResult.State(); + m_resultInformation = unitResult.ResultInformation(); + m_unit = unitResult.Unit(); + } + ConfigurationSetChangeEventType ConfigurationSetChangeData::Change() { return m_change; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h @@ -18,6 +18,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void Initialize(ConfigurationSetState state); void Initialize(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit); + void Initialize(const IApplyGroupMemberSettingsResult& unitResult); #endif ConfigurationSetChangeEventType Change(); diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp @@ -36,7 +36,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { std::vector<hstring> temp{ value.Size() }; value.GetMany(0, temp); - return winrt::single_threaded_vector<hstring>(std::move(temp)); + return winrt::multi_threaded_vector<hstring>(std::move(temp)); } } @@ -100,7 +100,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationUnit::Dependencies(std::vector<hstring>&& value) { - m_dependencies = winrt::single_threaded_vector<hstring>(std::move(value)); + m_dependencies = winrt::multi_threaded_vector<hstring>(std::move(value)); } Windows::Foundation::Collections::ValueSet ConfigurationUnit::Metadata() @@ -182,7 +182,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { if (!m_units) { - m_units = winrt::single_threaded_vector<Configuration::ConfigurationUnit>(); + m_units = winrt::multi_threaded_vector<Configuration::ConfigurationUnit>(); } } } @@ -208,7 +208,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationUnit::Units(std::vector<Configuration::ConfigurationUnit>&& value) { - m_units = winrt::single_threaded_vector<Configuration::ConfigurationUnit>(std::move(value)); + m_units = winrt::multi_threaded_vector<Configuration::ConfigurationUnit>(std::move(value)); } HRESULT STDMETHODCALLTYPE ConfigurationUnit::SetLifetimeWatcher(IUnknown* watcher) diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.h b/src/Microsoft.Management.Configuration/ConfigurationUnit.h @@ -65,7 +65,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation guid m_instanceIdentifier; hstring m_identifier; ConfigurationUnitIntent m_intent = ConfigurationUnitIntent::Apply; - Windows::Foundation::Collections::IVector<hstring> m_dependencies{ winrt::single_threaded_vector<hstring>() }; + Windows::Foundation::Collections::IVector<hstring> m_dependencies{ winrt::multi_threaded_vector<hstring>() }; Windows::Foundation::Collections::ValueSet m_metadata; Windows::Foundation::Collections::ValueSet m_settings; IConfigurationUnitProcessorDetails m_details{ nullptr }; diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp @@ -29,10 +29,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationUnitResultInformation::Initialize(const Configuration::IConfigurationUnitResultInformation& other) { - m_resultCode = other.ResultCode(); - m_description = other.Description(); - m_details = other.Details(); - m_resultSource = other.ResultSource(); + if (other) + { + m_resultCode = other.ResultCode(); + m_description = other.Description(); + m_details = other.Details(); + m_resultSource = other.ResultSource(); + } } void ConfigurationUnitResultInformation::Initialize(hresult resultCode, std::wstring_view description) diff --git a/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.cpp b/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.cpp @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DefaultSetGroupProcessor.h" +#include "ConfigurationSetApplyProcessor.h" +#include "ExceptionResultHelpers.h" +#include "TestGroupSettingsResult.h" +#include "TestSettingsResult.h" + +#include <AppInstallerStrings.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + // Determines whether a configuration unit should be tested. + bool ShouldTestDuringTest(const Configuration::ConfigurationUnit& unit) + { + return unit.IsActive() && unit.Intent() != ConfigurationUnitIntent::Inform; + } + } + + void DefaultSetGroupProcessor::Initialize(const ConfigurationSet& set, const IConfigurationSetProcessor& setProcessor, ConfigThreadGlobals& threadGlobals, bool consistencyCheckOnly) + { + m_set = set; + m_setProcessor = setProcessor; + m_threadGlobals = &threadGlobals; + m_consistencyCheckOnly = consistencyCheckOnly; + } + + Windows::Foundation::IInspectable DefaultSetGroupProcessor::Group() + { + return m_set; + } + + Windows::Foundation::IAsyncOperationWithProgress<ITestGroupSettingsResult, ITestSettingsResult> DefaultSetGroupProcessor::TestGroupSettingsAsync() + { + auto strongThis = get_strong(); + co_await resume_background(); + + auto progress = co_await get_progress_token(); + auto cancellation = co_await get_cancellation_token(); + cancellation.enable_propagation(); + + auto result = make_self<wil::details::module_count_wrapper<implementation::TestGroupSettingsResult>>(); + result->Group(m_set); + progress.set_result(*result); + + try + { + for (const auto& unit : m_set.Units()) + { + ThrowIf(cancellation()); + + AICLI_LOG_DIRECT(m_threadGlobals->GetDiagnosticLogger(), Config, Info, << "Testing configuration unit: `" << AppInstaller::Utility::ConvertToUTF8(unit.Identifier()) << "` [" << AppInstaller::Utility::ConvertToUTF8(unit.Type()) << ']'); + + ITestSettingsResult settingsResult; + ConfigurationTestResult testResult = ConfigurationTestResult::Unknown; + auto unitResult = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>(); + + if (ShouldTestDuringTest(unit)) + { + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = m_setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + // Check again as creating the unit processor could take time + ThrowIf(cancellation()); + + if (unitProcessor) + { + IConfigurationGroupProcessor groupProcessor = unitProcessor.try_as<IConfigurationGroupProcessor>(); + + if (groupProcessor) + { + auto testOperation = groupProcessor.TestGroupSettingsAsync(); + + testOperation.Progress([&](const auto&, const ITestSettingsResult& unitResult) + { + progress(unitResult); + }); + + ITestGroupSettingsResult groupResult = co_await testOperation; + + // Put all of the group's unit results in our unit results + for (const auto& groupUnitResult : groupResult.UnitResults()) + { + result->AppendUnitResult(groupUnitResult); + } + + // Convert group result into a unit result for the group + auto testSettingsResult = make_self<wil::details::module_count_wrapper<implementation::TestSettingsResult>>(); + testSettingsResult->Unit(unit); + testSettingsResult->TestResult(groupResult.TestResult()); + testSettingsResult->ResultInformation(groupResult.ResultInformation()); + settingsResult = *testSettingsResult; + } + else + { + try + { + settingsResult = unitProcessor.TestSettings(); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + } + } + } + else + { + testResult = ConfigurationTestResult::NotRun; + } + + if (FAILED(unitResult->ResultCode())) + { + testResult = ConfigurationTestResult::Failed; + } + + // Check if we need to construct our own result object + if (!settingsResult) + { + auto testSettingsResult = make_self<wil::details::module_count_wrapper<implementation::TestSettingsResult>>(); + testSettingsResult->Unit(unit); + testSettingsResult->TestResult(testResult); + testSettingsResult->ResultInformation(*unitResult); + settingsResult = *testSettingsResult; + } + + result->AppendUnitResult(settingsResult); + + try + { + progress(settingsResult); + } + CATCH_LOG(); + } + + co_return *result; + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), result->ResultInformationInternal()); + throw; + } + } + + Windows::Foundation::IAsyncOperationWithProgress<IApplyGroupSettingsResult, IApplyGroupMemberSettingsResult> DefaultSetGroupProcessor::ApplyGroupSettingsAsync() + { + auto strongThis = get_strong(); + co_await resume_background(); + + ConfigurationSetApplyProcessor applyProcessor{ m_set, m_setProcessor, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token() } }; + applyProcessor.Process(m_consistencyCheckOnly); + + co_return applyProcessor.Result(); + } + + void DefaultSetGroupProcessor::ThrowIf(bool cancellation) + { + if (cancellation) + { + AICLI_LOG_DIRECT(m_threadGlobals->GetDiagnosticLogger(), Config, Warning, << "Operation cancelled"); + throw winrt::hresult_canceled(); + } + } +} diff --git a/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.h b/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "ConfigThreadGlobals.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Implements the default group processing for configuration sets when the set processor doesn't handle it. + struct DefaultSetGroupProcessor : winrt::implements<DefaultSetGroupProcessor, IConfigurationGroupProcessor> + { + using ConfigurationSet = Configuration::ConfigurationSet; + + DefaultSetGroupProcessor() = default; + + void Initialize(const ConfigurationSet& set, const IConfigurationSetProcessor& setProcessor, ConfigThreadGlobals& threadGlobals, bool consistencyCheckOnly = false); + + IInspectable Group(); + + Windows::Foundation::IAsyncOperationWithProgress<ITestGroupSettingsResult, ITestSettingsResult> TestGroupSettingsAsync(); + + Windows::Foundation::IAsyncOperationWithProgress<IApplyGroupSettingsResult, IApplyGroupMemberSettingsResult> ApplyGroupSettingsAsync(); + + private: + void ThrowIf(bool cancellation); + + ConfigurationSet m_set = nullptr; + IConfigurationSetProcessor m_setProcessor; + ConfigThreadGlobals* m_threadGlobals = nullptr; + bool m_consistencyCheckOnly = false; + }; +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp b/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp @@ -7,7 +7,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { GetConfigurationSetDetailsResult::GetConfigurationSetDetailsResult() : - m_unitResults(single_threaded_vector<GetConfigurationUnitDetailsResult>()) + m_unitResults(multi_threaded_vector<GetConfigurationUnitDetailsResult>()) {}; const Windows::Foundation::Collections::IVector<GetConfigurationUnitDetailsResult>& GetConfigurationSetDetailsResult::UnitResultsVector() diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -505,6 +505,80 @@ namespace Microsoft.Management.Configuration IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit unit); } + // The result of applying an individual unit settings with an IConfigurationGroupProcessor. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IApplyGroupMemberSettingsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // The state of the unit. + // Properties other than `Unit` are not valid unless this value is `Completed`. + ConfigurationUnitState State{ get; }; + + // Will be true if the configuration unit was in the desired state (Test returns true) prior to the apply action. + Boolean PreviouslyInDesiredState{ get; }; + + // Indicates whether a reboot is required after the settings were applied. + Boolean RebootRequired{ get; }; + + // The result of applying the configuration unit. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of applying the settings with an IConfigurationGroupProcessor. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IApplyGroupSettingsResult + { + // The configuration group object (set or unit). + Object Group{ get; }; + + // Indicates whether a reboot is required after the settings were applied. + Boolean RebootRequired{ get; }; + + // The result of applying the configuration unit group. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // Results for each configuration unit in the group. + Windows.Foundation.Collections.IVector<IApplyGroupMemberSettingsResult> UnitResults{ get; }; + } + + // The result of testing the settings with an IConfigurationGroupProcessor. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface ITestGroupSettingsResult + { + // The configuration group object (set or unit). + Object Group{ get; }; + + // The result (if any) of running Test on the configuration unit group. + ConfigurationTestResult TestResult{ get; }; + + // The result of testing the configuration unit group. + // This is not the response for the test, but rather contains information about the actual attempt to run the test. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // Results for each configuration unit in the group. + Windows.Foundation.Collections.IVector<ITestSettingsResult> UnitResults{ get; }; + } + + // Provides access to a specific configuration unit group within the runtime. + // An object returned by `CreateUnitProcessor` or `CreateSetProcessor` can implement this interface to indicate that it can take + // the responsibility of executing the units that it contains. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IConfigurationGroupProcessor + { + // The configuration group object (set or unit). + Object Group{ get; }; + + // Determines if the system is already in the state described by the configuration unit group. + // Progress is expected for every descendant unit and finally the group unit itself. + Windows.Foundation.IAsyncOperationWithProgress<ITestGroupSettingsResult, ITestSettingsResult> TestGroupSettingsAsync(); + + // Applies the state described in the configuration unit group. + // Progress is expected for every descendant unit and finally the group unit itself. + Windows.Foundation.IAsyncOperationWithProgress<IApplyGroupSettingsResult, IApplyGroupMemberSettingsResult> ApplyGroupSettingsAsync(); + } + // The level of the diagnostic information. [contract(Microsoft.Management.Configuration.Contract, 1)] enum DiagnosticLevel diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -198,6 +198,7 @@ <ItemGroup> <ClInclude Include="ApplyConfigurationSetResult.h" /> <ClInclude Include="ApplyConfigurationUnitResult.h" /> + <ClInclude Include="ApplyGroupSettingsResult.h" /> <ClInclude Include="ArgumentValidation.h" /> <ClInclude Include="ConfigThreadGlobals.h" /> <ClInclude Include="ConfigurationChangeData.h" /> @@ -216,6 +217,7 @@ <ClInclude Include="ConfigurationStaticFunctions.h" /> <ClInclude Include="ConfigurationUnit.h" /> <ClInclude Include="ConfigurationUnitResultInformation.h" /> + <ClInclude Include="DefaultSetGroupProcessor.h" /> <ClInclude Include="DiagnosticInformationInstance.h" /> <ClInclude Include="ExceptionResultHelpers.h" /> <ClInclude Include="GetAllConfigurationUnitSettingsResult.h" /> @@ -229,10 +231,13 @@ <ClInclude Include="Telemetry\TraceLogging.h" /> <ClInclude Include="TestConfigurationSetResult.h" /> <ClInclude Include="TestConfigurationUnitResult.h" /> + <ClInclude Include="TestGroupSettingsResult.h" /> + <ClInclude Include="TestSettingsResult.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="ApplyConfigurationSetResult.cpp" /> <ClCompile Include="ApplyConfigurationUnitResult.cpp" /> + <ClCompile Include="ApplyGroupSettingsResult.cpp" /> <ClCompile Include="ArgumentValidation.cpp" /> <ClCompile Include="ConfigThreadGlobals.cpp" /> <ClCompile Include="ConfigurationChangeData.cpp" /> @@ -250,6 +255,7 @@ <ClCompile Include="ConfigurationStaticFunctions.cpp" /> <ClCompile Include="ConfigurationUnit.cpp" /> <ClCompile Include="ConfigurationUnitResultInformation.cpp" /> + <ClCompile Include="DefaultSetGroupProcessor.cpp" /> <ClCompile Include="DiagnosticInformationInstance.cpp" /> <ClCompile Include="GetAllConfigurationUnitSettingsResult.cpp" /> <ClCompile Include="GetConfigurationSetDetailsResult.cpp" /> @@ -264,6 +270,8 @@ <ClCompile Include="Telemetry\TraceLogging.cpp" /> <ClCompile Include="TestConfigurationSetResult.cpp" /> <ClCompile Include="TestConfigurationUnitResult.cpp" /> + <ClCompile Include="TestGroupSettingsResult.cpp" /> + <ClCompile Include="TestSettingsResult.cpp" /> </ItemGroup> <ItemGroup> <None Include="Microsoft_Management_Configuration.def" /> diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -90,6 +90,18 @@ <ClCompile Include="ConfigurationSetParser_0_3.cpp"> <Filter>Parser</Filter> </ClCompile> + <ClCompile Include="DefaultSetGroupProcessor.cpp"> + <Filter>Internals</Filter> + </ClCompile> + <ClCompile Include="TestGroupSettingsResult.cpp"> + <Filter>Internals</Filter> + </ClCompile> + <ClCompile Include="TestSettingsResult.cpp"> + <Filter>Internals</Filter> + </ClCompile> + <ClCompile Include="ApplyGroupSettingsResult.cpp"> + <Filter>Internals</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -189,6 +201,18 @@ <ClInclude Include="ConfigurationSetParser_0_3.h"> <Filter>Parser</Filter> </ClInclude> + <ClInclude Include="DefaultSetGroupProcessor.h"> + <Filter>Internals</Filter> + </ClInclude> + <ClInclude Include="TestGroupSettingsResult.h"> + <Filter>Internals</Filter> + </ClInclude> + <ClInclude Include="TestSettingsResult.h"> + <Filter>Internals</Filter> + </ClInclude> + <ClInclude Include="ApplyGroupSettingsResult.h"> + <Filter>Internals</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Midl Include="Microsoft.Management.Configuration.idl" /> @@ -215,4 +239,7 @@ <UniqueIdentifier>{5a02f1a5-14f3-4a28-8bed-212f3e6b1a00}</UniqueIdentifier> </Filter> </ItemGroup> + <ItemGroup> + <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" /> + </ItemGroup> </Project> \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp @@ -382,6 +382,23 @@ namespace winrt::Microsoft::Management::Configuration::implementation } CATCH_LOG(); + void TelemetryTraceLogger::LogConfigProcessingSummaryForApplyException( + const ConfigurationSet& configurationSet, + hresult error, + const ApplyConfigurationSetResult& result) const noexcept try + { + if (!IsTelemetryEnabled()) + { + return; + } + + ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); + + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.IsFromHistory(), ConfigurationUnitIntent::Apply, + error, ConfigurationUnitResultSource::Internal, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); + } + CATCH_LOG(); + bool TelemetryTraceLogger::IsTelemetryEnabled() const noexcept { #ifdef AICLI_DISABLE_TEST_HOOKS diff --git a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h @@ -109,6 +109,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation const ConfigurationSet& configurationSet, const ApplyConfigurationSetResult& result) const noexcept; + // Logs a processing summary event for a configuration set apply run exception. + void LogConfigProcessingSummaryForApplyException( + const ConfigurationSet& configurationSet, + hresult error, + const ApplyConfigurationSetResult& result) const noexcept; + protected: bool IsTelemetryEnabled() const noexcept; diff --git a/src/Microsoft.Management.Configuration/TestConfigurationSetResult.cpp b/src/Microsoft.Management.Configuration/TestConfigurationSetResult.cpp @@ -6,7 +6,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { - TestConfigurationSetResult::TestConfigurationSetResult() : m_unitResults(single_threaded_vector<TestConfigurationUnitResult>()) + TestConfigurationSetResult::TestConfigurationSetResult() : m_unitResults(multi_threaded_vector<TestConfigurationUnitResult>()) { } diff --git a/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.cpp b/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.cpp @@ -6,10 +6,18 @@ namespace winrt::Microsoft::Management::Configuration::implementation { - void TestConfigurationUnitResult::Initialize(ConfigurationUnit unit, IConfigurationUnitResultInformation resultInformation) + void TestConfigurationUnitResult::Initialize(const ITestSettingsResult& result) + { + m_unit = result.Unit(); + THROW_HR_IF(E_POINTER, !m_unit); + m_testResult = result.TestResult(); + m_resultInformation = result.ResultInformation(); + THROW_HR_IF(E_POINTER, !m_resultInformation); + } + + void TestConfigurationUnitResult::Unit(const ConfigurationUnit& unit) { m_unit = unit; - m_resultInformation = resultInformation; } ConfigurationUnit TestConfigurationUnitResult::Unit() diff --git a/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.h b/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.h @@ -12,7 +12,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation TestConfigurationUnitResult() = default; #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(ConfigurationUnit unit, IConfigurationUnitResultInformation resultInformation); + void Initialize(const ITestSettingsResult& result); + + void Unit(const ConfigurationUnit& unit); void ResultInformation(const IConfigurationUnitResultInformation& value); void TestResult(ConfigurationTestResult value); #endif diff --git a/src/Microsoft.Management.Configuration/TestGroupSettingsResult.cpp b/src/Microsoft.Management.Configuration/TestGroupSettingsResult.cpp @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestGroupSettingsResult.h" +#include "TestConfigurationSetResult.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + TestGroupSettingsResult::TestGroupSettingsResult() : + m_resultInformation(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()), + m_unitResults(winrt::multi_threaded_vector<ITestSettingsResult>()) + {} + + void TestGroupSettingsResult::Group(const Windows::Foundation::IInspectable& value) + { + m_group = value; + } + + void TestGroupSettingsResult::TestResult(ConfigurationTestResult value) + { + m_testResult = value; + } + + TestGroupSettingsResult::ResultInformationPtr TestGroupSettingsResult::ResultInformationInternal() + { + return m_resultInformation; + } + + void TestGroupSettingsResult::AppendUnitResult(const ITestSettingsResult& value) + { + m_unitResults.Append(value); + + // Also aggregate the result of this incoming test into the overall result + m_testResult = TestConfigurationSetResult::FoldInTestResult(m_testResult, value.TestResult()); + } + + Windows::Foundation::IInspectable TestGroupSettingsResult::Group() + { + return m_group; + } + + ConfigurationTestResult TestGroupSettingsResult::TestResult() + { + return m_testResult; + } + + IConfigurationUnitResultInformation TestGroupSettingsResult::ResultInformation() + { + return *m_resultInformation; + } + + Windows::Foundation::Collections::IVector<ITestSettingsResult> TestGroupSettingsResult::UnitResults() + { + return m_unitResults; + } +} diff --git a/src/Microsoft.Management.Configuration/TestGroupSettingsResult.h b/src/Microsoft.Management.Configuration/TestGroupSettingsResult.h @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct TestGroupSettingsResult : winrt::implements<TestGroupSettingsResult, ITestGroupSettingsResult> + { + TestGroupSettingsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + using ResultInformationPtr = decltype(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()); + + void Group(const Windows::Foundation::IInspectable& value); + void TestResult(ConfigurationTestResult value); + ResultInformationPtr ResultInformationInternal(); + void AppendUnitResult(const ITestSettingsResult& value); +#endif + + Windows::Foundation::IInspectable Group(); + ConfigurationTestResult TestResult(); + IConfigurationUnitResultInformation ResultInformation(); + Windows::Foundation::Collections::IVector<ITestSettingsResult> UnitResults(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::IInspectable m_group; + ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; + ResultInformationPtr m_resultInformation; + Windows::Foundation::Collections::IVector<ITestSettingsResult> m_unitResults; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/TestSettingsResult.cpp b/src/Microsoft.Management.Configuration/TestSettingsResult.cpp @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestSettingsResult.h" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void TestSettingsResult::Unit(const ConfigurationUnit& value) + { + m_unit = value; + } + + void TestSettingsResult::TestResult(ConfigurationTestResult value) + { + m_testResult = value; + } + + void TestSettingsResult::ResultInformation(const IConfigurationUnitResultInformation& value) + { + m_resultInformation = value; + } + + ConfigurationUnit TestSettingsResult::Unit() + { + return m_unit; + } + + ConfigurationTestResult TestSettingsResult::TestResult() + { + return m_testResult; + } + + IConfigurationUnitResultInformation TestSettingsResult::ResultInformation() + { + return m_resultInformation; + } +} diff --git a/src/Microsoft.Management.Configuration/TestSettingsResult.h b/src/Microsoft.Management.Configuration/TestSettingsResult.h @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct TestSettingsResult : winrt::implements<TestSettingsResult, ITestSettingsResult> + { + using ConfigurationUnit = Configuration::ConfigurationUnit; + + TestSettingsResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Unit(const ConfigurationUnit& value); + void TestResult(ConfigurationTestResult value); + void ResultInformation(const IConfigurationUnitResultInformation& value); +#endif + + ConfigurationUnit Unit(); + ConfigurationTestResult TestResult(); + IConfigurationUnitResultInformation ResultInformation(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationUnit m_unit = nullptr; + ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; + IConfigurationUnitResultInformation m_resultInformation; +#endif + }; +}