commit e5aced0393121600d7ddf07faf47c18a0b2f6d93 parent 2d77a56cbb965ad78577d9340e68e60acf3f471c Author: JohnMcPMS <johnmcp@microsoft.com> Date: Wed, 19 Jul 2023 12:13:58 -0700 Configure validate command (#3441) Implements the `configure validate` command that will help discover potential issues with a configuration file. More validation can be added in the future, but currently the flow is: 1. Open the file and report on any YAML / configuration schema errors 2. Determine if there are any semantic errors in the configuration (duplicate identifiers, missing dependencies, cycles in the graph) 3. Check for the availability of unit processors and warn on things like being non-public, prerelease (but not marked that way), or not available in any configured catalogs Diffstat:
40 files changed, 891 insertions(+), 114 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -9,6 +9,7 @@ agg aicli AICLIC alreadyinstalled +AMap amrutha ansistring APARTMENTTHREADED @@ -25,6 +26,7 @@ ARMNT arp arphelper ASAN +ASequence ashpatil Ashwini ASwitch @@ -434,6 +436,7 @@ TCpp tcs Templating temppath +testdata testexampleinstaller thiscouldbeapc threehundred diff --git a/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp @@ -2,12 +2,17 @@ // Licensed under the MIT License. #include "pch.h" #include "ConfigureValidateCommand.h" +#include "Workflows/ConfigurationFlow.h" + +using namespace AppInstaller::CLI::Workflow; namespace AppInstaller::CLI { std::vector<Argument> ConfigureValidateCommand::GetArguments() const { - return {}; + return { + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional, true }, + }; } Resource::LocString ConfigureValidateCommand::ShortDescription() const @@ -28,6 +33,13 @@ namespace AppInstaller::CLI void ConfigureValidateCommand::ExecuteInternal(Execution::Context& context) const { - Command::ExecuteInternal(context); + context << + VerifyIsFullPackage << + VerifyFile(Execution::Args::Type::ConfigurationFile) << + CreateConfigurationProcessor << + OpenConfigurationSet << + ValidateConfigurationSetSemantics << + ValidateConfigurationSetUnitProcessors << + ValidateAllGoodMessage; } } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -70,6 +70,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileEmpty); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalidYAML); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileVersionUnknown); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingDetails); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInDesiredState); @@ -97,16 +98,22 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedUnitProcessing); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasDuplicateIdentifier); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasMissingDependency); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitIsPartOfDependencyCycle); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitManuallySkipped); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleConflict); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleImportFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleNotProvidedWarning); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitMultipleMatches); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNeedsPrereleaseWarning); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFound); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFoundInModule); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotInCatalogWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotPublicWarning); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToDependency); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToFailedAssert); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitReturnedInvalidResult); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationValidationFoundNoIssues); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptApply); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -31,6 +31,7 @@ namespace AppInstaller::CLI::Workflow { constexpr std::wstring_view s_Directive_Description = L"description"; constexpr std::wstring_view s_Directive_Module = L"module"; + constexpr std::wstring_view s_Directive_AllowPrerelease = L"allowPrerelease"; Logging::Level ConvertLevel(DiagnosticLevel level) { @@ -99,6 +100,21 @@ namespace AppInstaller::CLI::Workflow return {}; } + std::optional<bool> GetValueSetBool(const ValueSet& valueSet, std::wstring_view value) + { + if (valueSet.HasKey(value)) + { + auto object = valueSet.Lookup(value); + IPropertyValue property = object.try_as<IPropertyValue>(); + if (property && property.Type() == PropertyType::Boolean) + { + return property.GetBoolean(); + } + } + + return {}; + } + void OutputPropertyValue(OutputStream& out, const IPropertyValue property) { switch (property.Type()) @@ -806,6 +822,8 @@ namespace AppInstaller::CLI::Workflow openResult = openAction.get(); } + progressScope.reset(); + if (FAILED_LOG(static_cast<HRESULT>(openResult.ResultCode().value))) { AICLI_LOG(Config, Error, << "Failed to open configuration set at " << absolutePath.u8string() << " with error 0x" << Logging::SetHRFormat << static_cast<HRESULT>(openResult.ResultCode().value)); @@ -827,7 +845,7 @@ namespace AppInstaller::CLI::Workflow case WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE: case WINGET_CONFIG_ERROR_INVALID_YAML: default: - context.Reporter.Error() << Resource::String::ConfigurationFileInvalid << std::endl; + context.Reporter.Error() << Resource::String::ConfigurationFileInvalidYAML << std::endl; break; } @@ -865,7 +883,7 @@ namespace AppInstaller::CLI::Workflow auto progressScope = context.Reporter.BeginAsyncProgress(true); progressScope->Callback().SetProgressMessage(gettingDetailString); - auto getDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailLevel::Catalog); + auto getDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::ReadOnly); auto unification = CreateProgressCancellationUnification(std::move(progressScope), getDetailsOperation); OutputStream out = context.Reporter.Info(); @@ -1038,4 +1056,250 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); } } + + void ValidateConfigurationSetSemantics(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>(); + + if (configContext.Set().ConfigurationUnits().Size() == 0) + { + context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl; + // This isn't an error termination, but there is no reason to proceed. + AICLI_TERMINATE_CONTEXT(S_FALSE); + } + + ApplyConfigurationSetResult result = configContext.Processor().ApplySet(configContext.Set(), ApplyConfigurationSetFlags::PerformConsistencyCheckOnly); + + if (FAILED(result.ResultCode())) + { + for (const auto& unitResult : result.UnitResults()) + { + IConfigurationUnitResultInformation resultInformation = unitResult.ResultInformation(); + winrt::hresult resultCode = resultInformation.ResultCode(); + + if (FAILED(resultCode)) + { + ConfigurationUnit unit = unitResult.Unit(); + + auto out = context.Reporter.Info(); + OutputConfigurationUnitHeader(out, unit, unit.UnitName()); + + switch (resultCode) + { + case WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitHasDuplicateIdentifier(Utility::LocIndString{ Utility::ConvertToUTF8(unit.Identifier()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_MISSING_DEPENDENCY: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitHasMissingDependency(Utility::LocIndString{ Utility::ConvertToUTF8(resultInformation.Details()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitIsPartOfDependencyCycle << std::endl; + break; + default: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitFailed(static_cast<int32_t>(resultCode)) << std::endl; + break; + } + } + } + + AICLI_TERMINATE_CONTEXT(result.ResultCode()); + } + } + + void ValidateConfigurationSetUnitProcessors(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>(); + + // TODO: We could optimize this by creating a set with unique resource units + + // First get the local details + auto gettingDetailString = Resource::String::ConfigurationGettingDetails(); + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + + auto getLocalDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Local); + auto unification = CreateProgressCancellationUnification(std::move(progressScope), getLocalDetailsOperation); + + HRESULT getLocalHR = S_OK; + GetConfigurationSetDetailsResult getLocalResult = nullptr; + + try + { + getLocalResult = getLocalDetailsOperation.get(); + } + catch (...) + { + getLocalHR = LOG_CAUGHT_EXCEPTION(); + } + + unification.Reset(); + + if (context.IsTerminated()) + { + // The context should only be terminated on us due to cancellation + context.Reporter.Error() << Resource::String::Cancelled << std::endl; + return; + } + + if (FAILED(getLocalHR)) + { + // Failing to get details might not be fatal, warn about it but proceed + context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl; + } + + // Next get the details from the catalog + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + + auto getCatalogDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Catalog); + unification = CreateProgressCancellationUnification(std::move(progressScope), getCatalogDetailsOperation); + + HRESULT getCatalogHR = S_OK; + GetConfigurationSetDetailsResult getCatalogResult = nullptr; + + try + { + getCatalogResult = getCatalogDetailsOperation.get(); + } + catch (...) + { + getCatalogHR = LOG_CAUGHT_EXCEPTION(); + } + + unification.Reset(); + + if (context.IsTerminated()) + { + // The context should only be terminated on us due to cancellation + context.Reporter.Error() << Resource::String::Cancelled << std::endl; + return; + } + + if (FAILED(getCatalogHR)) + { + // Failing to get the catalog details means that we can't really get give much of a meaningful response. + context.Reporter.Error() << Resource::String::ConfigurationFailedToGetDetails << std::endl; + AICLI_TERMINATE_CONTEXT(getCatalogHR); + } + + auto units = configContext.Set().ConfigurationUnits(); + auto localUnitResults = getLocalResult ? getLocalResult.UnitResults() : nullptr; + if (localUnitResults && units.Size() != localUnitResults.Size()) + { + AICLI_LOG(Config, Error, << "The details result size did not match the set size: Set[" << units.Size() << "], Local[" << localUnitResults.Size() << "]"); + THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED); + } + + auto catalogUnitResults = getCatalogResult.UnitResults(); + if (units.Size() != catalogUnitResults.Size()) + { + AICLI_LOG(Config, Error, << "The details result sizes did not match the set size: Set[" << units.Size() << "], Catalog[" << catalogUnitResults.Size() << "]"); + THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED); + } + + bool foundIssue = false; + + // Now that we have the entire set of local and catalog details, process each unit + for (uint32_t i = 0; i < units.Size(); ++i) + { + const ConfigurationUnit& unit = units.GetAt(i); + GetConfigurationUnitDetailsResult localUnitResult = localUnitResults ? localUnitResults.GetAt(i) : nullptr; + GetConfigurationUnitDetailsResult catalogUnitResult = catalogUnitResults.GetAt(i); + IConfigurationUnitProcessorDetails catalogDetails = catalogUnitResult.Details(); + + bool needsHeader = true; + auto outputHeaderIfNeeded = [&]() + { + if (needsHeader) + { + auto out = context.Reporter.Info(); + OutputConfigurationUnitHeader(out, unit, unit.UnitName()); + needsHeader = false; + foundIssue = true; + } + }; + + if (GetValueSetString(unit.Directives(), s_Directive_Module).value_or(Utility::LocIndString{}).empty()) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitModuleNotProvidedWarning << std::endl; + } + + if (catalogDetails) + { + // Warn if unit is not public + if (!catalogDetails.IsPublic()) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNotPublicWarning << std::endl; + } + + // Since it is available, no more checks are needed + continue; + } + // Everything below here is due to not finding in the catalog search + + if (FAILED(catalogUnitResult.ResultInformation().ResultCode())) + { + outputHeaderIfNeeded(); + OutputUnitRunFailure(context, unit, catalogUnitResult.ResultInformation()); + continue; + } + + // If not already prerelease, try with prerelease and warn if found + std::optional<bool> allowPrereleaseDirective = GetValueSetBool(unit.Directives(), s_Directive_AllowPrerelease); + if (!allowPrereleaseDirective || !allowPrereleaseDirective.value()) + { + // Check if the configuration unit is prerelease but the author forgot it + ConfigurationUnit clone = unit.Copy(); + clone.Directives().Insert(s_Directive_AllowPrerelease, PropertyValue::CreateBoolean(true)); + + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + + auto getUnitDetailsOperation = configContext.Processor().GetUnitDetailsAsync(clone, ConfigurationUnitDetailFlags::Catalog); + auto unitUnification = CreateProgressCancellationUnification(std::move(progressScope), getUnitDetailsOperation); + + IConfigurationUnitProcessorDetails prereleaseDetails; + + try + { + prereleaseDetails = getUnitDetailsOperation.get().Details(); + } + CATCH_LOG(); + + unification.Reset(); + + if (prereleaseDetails) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNeedsPrereleaseWarning << std::endl; + continue; + } + } + + // If module is local, warn that we couldn't find it in the catalog + if (localUnitResult && localUnitResult.Details()) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNotInCatalogWarning << std::endl; + continue; + } + + // Finally, error that we couldn't find it at all + outputHeaderIfNeeded(); + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitNotFound << std::endl; + } + + if (foundIssue) + { + // Indicate that it was not a total success + AICLI_TERMINATE_CONTEXT(S_FALSE); + } + } + + void ValidateAllGoodMessage(Execution::Context& context) + { + context.Reporter.Info() << Resource::String::ConfigurationValidationFoundNoIssues << std::endl; + } } diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h @@ -60,4 +60,22 @@ namespace AppInstaller::CLI::Workflow // Inputs: None // Outputs: None void VerifyIsFullPackage(Execution::Context& context); + + // Validates the configuration set semantically. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ValidateConfigurationSetSemantics(Execution::Context& context); + + // Validates that the unit processors referenced by the set are valid/available/etc. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ValidateConfigurationSetUnitProcessors(Execution::Context& context); + + // Outputs the final message stating that no issues were found. + // Required Args: None + // Inputs: None + // Outputs: None + void ValidateAllGoodMessage(Execution::Context& context); } diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -49,6 +49,22 @@ </ItemGroup> <ItemGroup> + <None Remove="testdata\configuration\DependencyCycle.yml" /> + <None Remove="testdata\configuration\DuplicateIdentifiers.yml" /> + <None Remove="testdata\configuration\Empty.yml" /> + <None Remove="testdata\configuration\MissingDependency.yml" /> + <None Remove="testdata\configuration\ModuleMismatch.yml" /> + <None Remove="testdata\configuration\modules\NoVersion.yml" /> + <None Remove="testdata\configuration\NoResourceName.yml" /> + <None Remove="testdata\configuration\NotConfig.yml" /> + <None Remove="TestData\Configuration\PSGallery_NoSettings.yml" /> + <None Remove="testdata\configuration\ResourceNotFound.yml" /> + <None Remove="testdata\configuration\ResourcesNotASequence.yml" /> + <None Remove="testdata\configuration\UnitNotAMap.yml" /> + <None Remove="testdata\configuration\UnknownVersion.yml" /> + </ItemGroup> + + <ItemGroup> <Content Include="..\..\doc\admx\DesktopAppInstaller.admx" Link="TestData\DesktopAppInstaller.admx"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> diff --git a/src/AppInstallerCLIE2ETests/ConfigureValidateCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureValidateCommand.cs @@ -0,0 +1,203 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigureValidateCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + + /// <summary> + /// `Configure validate` command tests. + /// </summary> + public class ConfigureValidateCommand + { + private const string Command = "configure validate"; + + /// <summary> + /// Setup done once before all the tests here. + /// </summary> + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("configuration", true); + } + + /// <summary> + /// The configuration file is empty. + /// </summary> + [Test] + public void EmptyFile() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\Empty.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_YAML, result.ExitCode); + } + + /// <summary> + /// The configuration file is not configuration YAML. + /// </summary> + [Test] + public void NotConfigurationYAML() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NotConfig.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_FIELD, result.ExitCode); + Assert.True(result.StdOut.Contains("properties")); + Assert.True(result.StdOut.Contains("missing")); + } + + /// <summary> + /// The configuration file does not specify the schema version. + /// </summary> + [Test] + public void NoVersion() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NoVersion.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_FIELD, result.ExitCode); + Assert.True(result.StdOut.Contains("configurationVersion")); + Assert.True(result.StdOut.Contains("missing")); + } + + /// <summary> + /// The configuration file schema version is not known. + /// </summary> + [Test] + public void UnknownVersion() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\UnknownVersion.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, result.ExitCode); + Assert.True(result.StdOut.Contains("Configuration file version")); + Assert.True(result.StdOut.Contains("is not known.")); + } + + /// <summary> + /// The resources node is not the correct type in YAML. + /// </summary> + [Test] + public void ResourcesIsWrongType() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ResourcesNotASequence.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_TYPE, result.ExitCode); + Assert.True(result.StdOut.Contains("resources")); + Assert.True(result.StdOut.Contains("wrong type")); + } + + /// <summary> + /// The unit node is not the correct type in YAML. + /// </summary> + [Test] + public void UnitIsWrongType() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\UnitNotAMap.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_TYPE, result.ExitCode); + Assert.True(result.StdOut.Contains("resources[0]")); + Assert.True(result.StdOut.Contains("wrong type")); + } + + /// <summary> + /// The resource name is missing. + /// </summary> + [Test] + public void NoResourceName() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NoResourceName.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); + Assert.True(result.StdOut.Contains("resource")); + Assert.True(result.StdOut.Contains("invalid value")); + Assert.True(result.StdOut.Contains("Module/")); + } + + /// <summary> + /// The resource name module does not match the directives module. + /// </summary> + [Test] + public void ModuleMismatch() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ModuleMismatch.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); + Assert.True(result.StdOut.Contains("module")); + Assert.True(result.StdOut.Contains("invalid value")); + Assert.True(result.StdOut.Contains("DifferentModule")); + } + + /// <summary> + /// The configuration contains multiple resources with the same identifier. + /// </summary> + [Test] + public void DuplicateIdentifiers() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\DuplicateIdentifiers.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_DUPLICATE_IDENTIFIER, result.ExitCode); + Assert.True(result.StdOut.Contains("The configuration contains the identifier `same` multiple times.")); + Assert.False(result.StdOut.Contains("NotMentioned")); + } + + /// <summary> + /// The configuration does not contain the dependency. + /// </summary> + [Test] + public void MissingDependency() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\MissingDependency.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_DEPENDENCY, result.ExitCode); + Assert.True(result.StdOut.Contains("The dependency `missing` was not found within the configuration.")); + Assert.False(result.StdOut.Contains("xE2ETestResource")); + } + + /// <summary> + /// The configuration contains a dependency cycle. + /// </summary> + [Test] + public void DependencyCycle() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\DependencyCycle.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_DEPENDENCY_CYCLE, result.ExitCode); + Assert.True(result.StdOut.Contains("This configuration unit is part of a dependency cycle.")); + Assert.False(result.StdOut.Contains("NotMentioned")); + } + + /// <summary> + /// The configuration unit is not available in a public catalog. + /// </summary> + [Test] + public void ResourceIsNotPublic() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("not available publicly")); + } + + /// <summary> + /// The configuration unit is not found. + /// </summary> + [Test] + public void ResourceIsNotFound() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ResourceNotFound.yml")); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("The configuration unit could not be found.")); + } + + /// <summary> + /// The module was not provided. + /// </summary> + [Test] + public void ModuleNotProvided() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoModule_NoSettings.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("The module was not provided.")); + } + + /// <summary> + /// No issues detected (yet). + /// </summary> + [Test] + public void NoIssuesDetected() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoSettings.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Validation found no issues.")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/DependencyCycle.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/DependencyCycle.yml @@ -0,0 +1,23 @@ +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + id: A + dependsOn: + - B + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + id: B + dependsOn: + - A + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt + Content: Contents! + - resource: NotMentioned + settings: + A: B diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/DuplicateIdentifiers.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/DuplicateIdentifiers.yml @@ -0,0 +1,19 @@ +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + id: same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + id: same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt + Content: Contents! + - resource: NotMentioned + settings: + A: B diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Empty.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/Empty.yml diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/MissingDependency.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/MissingDependency.yml @@ -0,0 +1,22 @@ +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + id: same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + dependsOn: + - same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt + Content: Contents! + - resource: MissingDependency + dependsOn: + - missing + settings: + A: B diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ModuleMismatch.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ModuleMismatch.yml @@ -0,0 +1,9 @@ +properties: + configurationVersion: 0.2 + resources: + - resource: Module/Resource + id: Identifier + directives: + module: DifferentModule + settings: + SettingInt: 1+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/NoResourceName.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/NoResourceName.yml @@ -0,0 +1,7 @@ +properties: + configurationVersion: 0.2 + resources: + - resource: Module/ + id: Identifier + settings: + SettingInt: 1+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/NoVersion.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/NoVersion.yml @@ -0,0 +1,2 @@ +properties: + value: 1+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/NotConfig.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/NotConfig.yml @@ -0,0 +1 @@ +yaml: true+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoSettings.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoSettings.yml @@ -0,0 +1,6 @@ +properties: + configurationVersion: 0.2 + resources: + - resource: XmlContentDsc/XmlFileContentResource + directives: + description: Set XML file contents diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceNotFound.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceNotFound.yml @@ -0,0 +1,8 @@ +properties: + configurationVersion: 0.2 + resources: + - resource: moduleThatDoesNotExist/resourceThatDoesNotExist + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourcesNotASequence.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourcesNotASequence.yml @@ -0,0 +1,3 @@ +properties: + configurationVersion: 0.1 + resources: 1+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/UnitNotAMap.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/UnitNotAMap.yml @@ -0,0 +1,4 @@ +properties: + configurationVersion: 0.1 + resources: + - string+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/UnknownVersion.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/UnknownVersion.yml @@ -0,0 +1,2 @@ +properties: + configurationVersion: 99999999+ \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -80,8 +80,8 @@ <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.ConfigurationConflictSetting>" InterfaceId="EB1E5A3C-A444-5394-B7B3-F1593937E31E" /> <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.ConfigurationSet>" InterfaceId="070C1D82-67BC-5F8E-BE2F-F0F66E70E2CE" /> - <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.ConfigurationUnit>" InterfaceId="DB35BA1B-3DE5-50B7-80CE-BE149DF0540C" /> - <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.GetConfigurationUnitDetailsResult>" InterfaceId="DCF7323D-3E1E-5B22-A331-C6A29CBB6D33" /> + <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.ConfigurationUnit>" InterfaceId="1E33B829-D292-55A6-A7DB-2903566A0BA0" /> + <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.IConfigurationUnitSettingDetails>" InterfaceId="9901CFD7-A9E3-5D2A-A79C-72FE20513823" /> <Interface Name="Windows.Foundation.Collections.IIterable`1<Microsoft.Management.Configuration.TestConfigurationUnitResult>" InterfaceId="73848262-86D4-5FFC-8353-8408C4E649DE" /> </ProxyStub> diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1999,6 +1999,29 @@ Please specify one of them using the --source option to proceed.</value> <data name="ConfigurationWarningPromptTest" xml:space="preserve"> <value>Have you reviewed the configuration and would you like to proceed verifying it against the system?</value> </data> + <data name="ConfigurationFileInvalidYAML" xml:space="preserve"> + <value>The configuration file is not a valid YAML file.</value> + <comment>{Locked="YAML"} YAML is a file format name.</comment> + </data> + <data name="ConfigurationUnitIsPartOfDependencyCycle" xml:space="preserve"> + <value>This configuration unit is part of a dependency cycle.</value> + </data> + <data name="ConfigurationValidationFoundNoIssues" xml:space="preserve"> + <value>Validation found no issues.</value> + </data> + <data name="ConfigurationUnitNeedsPrereleaseWarning" xml:space="preserve"> + <value>The configuration unit is only available as a prerelease, but it is not marked that way in the configuration. Add `allowPrerelease: true` to the `directives`.</value> + <comment>{Locked="allowPrerelease: true","directives"} These are values in the configuration file that are not localized.</comment> + </data> + <data name="ConfigurationUnitNotInCatalogWarning" xml:space="preserve"> + <value>The configuration unit was found locally, but could not be found in any configured catalog. Ensure that it is present on any system before applying the configuration.</value> + </data> + <data name="ConfigurationUnitNotPublicWarning" xml:space="preserve"> + <value>The configuration unit is not available publicly; ensure that anyone who will use this configuration has access to it.</value> + </data> + <data name="ConfigurationUnitModuleNotProvidedWarning" xml:space="preserve"> + <value>The module was not provided. Specifying the module improves performance and prevents future name collisions.</value> + </data> <data name="DownloadCommandLongDescription" xml:space="preserve"> <value>Downloads the installer from the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, download command will download the appropriate installer to the user's Downloads folder.</value> </data> diff --git a/src/AppInstallerCLITests/TestConfiguration.cpp b/src/AppInstallerCLITests/TestConfiguration.cpp @@ -40,11 +40,11 @@ namespace TestCommon { } - IConfigurationUnitProcessorDetails TestConfigurationSetProcessor::GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel) + IConfigurationUnitProcessorDetails TestConfigurationSetProcessor::GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) { if (GetUnitProcessorDetailsFunc) { - return GetUnitProcessorDetailsFunc(unit, detailLevel); + return GetUnitProcessorDetailsFunc(unit, detailFlags); } else { diff --git a/src/AppInstallerCLITests/TestConfiguration.h b/src/AppInstallerCLITests/TestConfiguration.h @@ -29,11 +29,11 @@ namespace TestCommon { winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails GetUnitProcessorDetails( const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit, - winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailLevel detailLevel); + winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailFlags detailFlags); std::function<winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails( const winrt::Microsoft::Management::Configuration::ConfigurationUnit&, - winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailLevel)> GetUnitProcessorDetailsFunc; + winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailFlags)> GetUnitProcessorDetailsFunc; winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessor CreateUnitProcessor( const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit, @@ -84,7 +84,7 @@ namespace TestCommon winrt::Windows::Foundation::DateTime PublishedDateValue; winrt::Windows::Foundation::DateTime PublishedDate() const { return PublishedDateValue; } - bool IsLocalValue; + bool IsLocalValue = false; bool IsLocal() const { return IsLocalValue; } winrt::hstring AuthorValue; @@ -99,7 +99,7 @@ namespace TestCommon winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::Management::Configuration::IConfigurationUnitSettingDetails> SettingsValue; winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Management::Configuration::IConfigurationUnitSettingDetails> Settings() const { return (SettingsValue ? SettingsValue.GetView() : nullptr); } - bool IsPublicValue; + bool IsPublicValue = false; bool IsPublic() const { return IsPublicValue; } }; diff --git a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs @@ -80,27 +80,33 @@ namespace Microsoft.Management.Configuration.Processor.Set /// Gets the configuration unit processor details for the given unit. /// </summary> /// <param name="unit">Configuration unit.</param> - /// <param name="detailLevel">Detail level.</param> + /// <param name="detailFlags">Detail flags.</param> /// <returns>Configuration unit processor details.</returns> public IConfigurationUnitProcessorDetails? GetUnitProcessorDetails( ConfigurationUnit unit, - ConfigurationUnitDetailLevel detailLevel) + ConfigurationUnitDetailFlags detailFlags) { try { var unitInternal = new ConfigurationUnitInternal(unit, this.configurationSet.Path); - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailLevel}] for: {unitInternal.ToIdentifyingString()}"); - var dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailFlags}] for: {unitInternal.ToIdentifyingString()}"); + + // (Local | Download | Load) will all work off of local files, so if any one is an option just use the local module info if found. + DscResourceInfoInternal? dscResourceInfo = null; + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Local) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) + { + dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + } if (dscResourceInfo is not null) { return this.GetUnitProcessorDetailsLocal( dscResourceInfo.Name, dscResourceInfo, - detailLevel == ConfigurationUnitDetailLevel.Load); + detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)); } - if (detailLevel == ConfigurationUnitDetailLevel.Local) + if (!(detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load))) { // Not found locally. return null; @@ -118,7 +124,7 @@ namespace Microsoft.Management.Configuration.Processor.Set dynamic foundModuleInfo = foundModule; - if (detailLevel == ConfigurationUnitDetailLevel.Catalog) + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog)) { return new ConfigurationUnitProcessorDetails( resourceName, @@ -128,7 +134,7 @@ namespace Microsoft.Management.Configuration.Processor.Set null); } - if (detailLevel == ConfigurationUnitDetailLevel.Download) + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download)) { var tempSavePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(tempSavePath); @@ -145,7 +151,7 @@ namespace Microsoft.Management.Configuration.Processor.Set this.GetCertificates(moduleInfo)); } - if (detailLevel == ConfigurationUnitDetailLevel.Load) + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) { this.ProcessorEnvironment.InstallModule(foundModule); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs @@ -68,9 +68,9 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers /// Gets the unit processor details for the given unit. /// </summary> /// <param name="unit">The unit.</param> - /// <param name="detailLevel">The detail level requested.</param> + /// <param name="detailFlags">The detail flags.</param> /// <returns>The details requested.</returns> - public IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel) + public IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) { if (this.Exceptions.ContainsKey(unit)) { @@ -79,7 +79,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers if (!this.Details.ContainsKey(unit)) { - this.Details.Add(unit, new TestConfigurationUnitProcessorDetails(unit, detailLevel)); + this.Details.Add(unit, new TestConfigurationUnitProcessorDetails(unit, detailFlags)); } return this.Details[unit]; @@ -100,11 +100,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers /// Creates a new unit processor details for the given unit. /// </summary> /// <param name="unit">The unit.</param> - /// <param name="detailLevel">The detail level requested.</param> + /// <param name="detailFlags">The detail flags.</param> /// <returns>The details requested.</returns> - internal TestConfigurationUnitProcessorDetails CreateUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel) + internal TestConfigurationUnitProcessorDetails CreateUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) { - this.Details[unit] = new TestConfigurationUnitProcessorDetails(unit, detailLevel); + this.Details[unit] = new TestConfigurationUnitProcessorDetails(unit, detailFlags); return this.Details[unit]; } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessorDetails.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessorDetails.cs @@ -16,17 +16,17 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers internal class TestConfigurationUnitProcessorDetails : IConfigurationUnitProcessorDetails { private ConfigurationUnit unit; - private ConfigurationUnitDetailLevel detailLevel; + private ConfigurationUnitDetailFlags detailFlags; /// <summary> /// Initializes a new instance of the <see cref="TestConfigurationUnitProcessorDetails"/> class. /// </summary> /// <param name="unit">The unit.</param> - /// <param name="detailLevel">The level of the details.</param> - internal TestConfigurationUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel) + /// <param name="detailFlags">The flags of the details.</param> + internal TestConfigurationUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) { this.unit = unit; - this.detailLevel = detailLevel; + this.detailFlags = detailFlags; } #pragma warning disable SA1600 // Elements should be documented diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTelemetryTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTelemetryTests.cs @@ -226,11 +226,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests public void CreateDetails(bool isPublic = true) { #pragma warning disable CS8602 // Dereference of a possibly null reference. - this.UnitDetails = this.Factory.NullProcessor.CreateUnitDetails(this.Unit, ConfigurationUnitDetailLevel.Catalog); + this.UnitDetails = this.Factory.NullProcessor.CreateUnitDetails(this.Unit, ConfigurationUnitDetailFlags.ReadOnly); #pragma warning restore CS8602 // Dereference of a possibly null reference. this.UnitDetails.IsPublic = isPublic; - this.Processor?.GetUnitDetails(this.Unit, ConfigurationUnitDetailLevel.Catalog); + this.Processor?.GetUnitDetails(this.Unit, ConfigurationUnitDetailFlags.ReadOnly); } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs @@ -344,7 +344,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Local); + ConfigurationUnitDetailFlags.Local); Assert.Null(configurationUnitProcessorDetails); @@ -357,7 +357,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests [Fact] public void GetUnitProcessorDetails_Local_NotInstalledByPowerShellGet() { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); PSObject? nullPsModuleInfo = null; @@ -385,7 +385,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Local); + ConfigurationUnitDetailFlags.Local); Assert.NotNull(configurationUnitProcessorDetails); Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitName); @@ -396,14 +396,14 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests /// <summary> /// Test GetUnitProcessorDetails locally found. Do not include Load. /// </summary> - /// <param name="detailLevel">Detail level.</param> + /// <param name="detailFlags">Detail flags.</param> [Theory] - [InlineData(ConfigurationUnitDetailLevel.Local)] - [InlineData(ConfigurationUnitDetailLevel.Catalog)] - [InlineData(ConfigurationUnitDetailLevel.Download)] - public void GetUnitProcessorDetails_Local(ConfigurationUnitDetailLevel detailLevel) + [InlineData(ConfigurationUnitDetailFlags.Local)] + [InlineData(ConfigurationUnitDetailFlags.ReadOnly)] + [InlineData(ConfigurationUnitDetailFlags.Download)] + public void GetUnitProcessorDetails_Local(ConfigurationUnitDetailFlags detailFlags) { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); var getModuleInfo = this.CreateGetModuleInfo(); @@ -431,7 +431,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - detailLevel); + detailFlags); Assert.NotNull(configurationUnitProcessorDetails); Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitName); @@ -447,7 +447,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests [Fact] public void GetUnitProcessorDetails_Local_Load() { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); var getModuleInfo = this.CreateGetModuleInfo(); @@ -478,7 +478,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Load); + ConfigurationUnitDetailFlags.Load); Assert.NotNull(configurationUnitProcessorDetails); Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitName); @@ -493,7 +493,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests [Fact] public void GetUnitProcessorDetails_Catalog_NotFound() { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); DscResourceInfoInternal? nullDscResourceInfo = null; PSObject? nullPsModuleInfo = null; @@ -513,7 +513,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Catalog); + ConfigurationUnitDetailFlags.ReadOnly); Assert.Null(configurationUnitProcessorDetails); @@ -526,7 +526,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests [Fact] public void GetUnitProcessorDetails_Catalog() { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); DscResourceInfoInternal? nullDscResourceInfo = null; var getFindResourceInfo = this.CreateFindResourceInfo(); @@ -546,7 +546,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Catalog); + ConfigurationUnitDetailFlags.ReadOnly); Assert.NotNull(configurationUnitProcessorDetails); Assert.Equal("SimpleFileResource", configurationUnitProcessorDetails.UnitName); @@ -560,7 +560,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests [Fact] public void GetUnitProcessorDetails_Download() { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); DscResourceInfoInternal? nullDscResourceInfo = null; var (_, psModuleInfo) = this.GetResourceAndModuleInfo(unit); var getFindModuleInfo = this.CreateGetModuleInfo(); @@ -592,7 +592,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Download); + ConfigurationUnitDetailFlags.Download); Assert.NotNull(configurationUnitProcessorDetails); Assert.Equal("SimpleFileResource", configurationUnitProcessorDetails.UnitName); @@ -608,7 +608,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests [Fact] public void GetUnitProcessorDetails_Load_NotFoundAfterInstall() { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); DscResourceInfoInternal? nullDscResourceInfo = null; var (_, psModuleInfo) = this.GetResourceAndModuleInfo(unit); var getFindResourceInfo = this.CreateFindResourceInfo(); @@ -632,7 +632,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Throws<InstallDscResourceException>(() => configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Load)); + ConfigurationUnitDetailFlags.Load)); processorEnvMock.Verify(); processorEnvMock.Verify( @@ -646,7 +646,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests [Fact] public void GetUnitProcessorDetails_Load() { - var unit = this.CreteConfigurationUnit(); + var unit = this.CreateConfigurationUnit(); DscResourceInfoInternal? nullDscResourceInfo = null; var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); var getFindResourceInfo = this.CreateFindResourceInfo(); @@ -677,7 +677,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( unit, - ConfigurationUnitDetailLevel.Load); + ConfigurationUnitDetailFlags.Load); Assert.NotNull(configurationUnitProcessorDetails); Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitName); @@ -725,7 +725,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests unitProcessor.TestSettings(); } - private ConfigurationUnit CreteConfigurationUnit() + private ConfigurationUnit CreateConfigurationUnit() { var unit = new ConfigurationUnit(); unit.UnitName = "SimpleFileResource"; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorGetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorGetTests.cs @@ -13,6 +13,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using Microsoft.VisualBasic; using Xunit; using Xunit.Abstractions; + using Xunit.Sdk; /// <summary> /// Unit tests for getting details on processors. @@ -41,11 +42,17 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); factory.NullProcessor = new TestConfigurationSetProcessor(null); - factory.NullProcessor.Exceptions.Add(configurationUnitThrows, new FileNotFoundException()); + var thrownException = new FileNotFoundException(); + factory.NullProcessor.Exceptions.Add(configurationUnitThrows, thrownException); ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - Assert.Throws<FileNotFoundException>(() => processor.GetUnitDetails(configurationUnitThrows, ConfigurationUnitDetailLevel.Local)); + GetConfigurationUnitDetailsResult result = processor.GetUnitDetails(configurationUnitThrows, ConfigurationUnitDetailFlags.Local); + + Assert.Null(result.Details); + Assert.Equal(configurationUnitThrows, result.Unit); + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); } /// <summary> @@ -60,7 +67,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); Assert.Null(configurationUnit.Details); - processor.GetUnitDetails(configurationUnit, ConfigurationUnitDetailLevel.Local); + processor.GetUnitDetails(configurationUnit, ConfigurationUnitDetailFlags.Local); Assert.NotNull(configurationUnit.Details); } @@ -82,7 +89,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - GetConfigurationSetDetailsResult result = processor.GetSetDetails(configurationSet, ConfigurationUnitDetailLevel.Local); + GetConfigurationSetDetailsResult result = processor.GetSetDetails(configurationSet, ConfigurationUnitDetailFlags.Local); var unitResults = result.UnitResults; Assert.Equal(2, unitResults.Count); @@ -111,7 +118,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - processor.GetSetDetails(configurationSet, ConfigurationUnitDetailLevel.Local); + processor.GetSetDetails(configurationSet, ConfigurationUnitDetailFlags.Local); Assert.NotNull(configurationUnit1.Details); Assert.NotNull(configurationUnit2.Details); } diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp @@ -313,13 +313,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation co_return CheckForConflicts(configurationSets, includeConfigurationHistory); } - Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel) + Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return GetSetDetailsImpl(configurationSet, detailLevel); + return GetSetDetailsImpl(configurationSet, detailFlags); } - Windows::Foundation::IAsyncOperationWithProgress<Configuration::GetConfigurationSetDetailsResult, Configuration::GetConfigurationUnitDetailsResult> ConfigurationProcessor::GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel) + Windows::Foundation::IAsyncOperationWithProgress<Configuration::GetConfigurationSetDetailsResult, Configuration::GetConfigurationUnitDetailsResult> ConfigurationProcessor::GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); @@ -328,12 +328,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation co_await winrt::resume_background(); - co_return GetSetDetailsImpl(localSet, detailLevel, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token()}); + co_return GetSetDetailsImpl(localSet, detailFlags, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token()}); } Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetailsImpl( const ConfigurationSet& configurationSet, - ConfigurationUnitDetailLevel detailLevel, + ConfigurationUnitDetailFlags detailFlags, AppInstaller::WinRT::AsyncProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> progress) { auto threadGlobals = m_threadGlobals.SetForCurrentThread(); @@ -354,7 +354,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation try { - IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailLevel); + IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailFlags); + unitResult->Details(details); get_self<implementation::ConfigurationUnit>(unit)->Details(std::move(details)); } catch (...) @@ -369,13 +370,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation return *result; } - void ConfigurationProcessor::GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel) + Configuration::GetConfigurationUnitDetailsResult ConfigurationProcessor::GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return GetUnitDetailsImpl(unit, detailLevel); + return GetUnitDetailsImpl(unit, detailFlags); } - Windows::Foundation::IAsyncAction ConfigurationProcessor::GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel) + Windows::Foundation::IAsyncOperation<Configuration::GetConfigurationUnitDetailsResult> ConfigurationProcessor::GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); @@ -384,16 +385,32 @@ namespace winrt::Microsoft::Management::Configuration::implementation co_await winrt::resume_background(); - co_return GetUnitDetailsImpl(localUnit, detailLevel); + co_return GetUnitDetailsImpl(localUnit, detailFlags); } - void ConfigurationProcessor::GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel) + Configuration::GetConfigurationUnitDetailsResult ConfigurationProcessor::GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) { auto threadGlobals = m_threadGlobals.SetForCurrentThread(); IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailLevel); - get_self<implementation::ConfigurationUnit>(unit)->Details(std::move(details)); + + auto unitResult = make_self<wil::details::module_count_wrapper<implementation::GetConfigurationUnitDetailsResult>>(); + auto unitResultInformation = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>(); + unitResult->Unit(unit); + unitResult->ResultInformation(*unitResultInformation); + + try + { + IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailFlags); + unitResult->Details(details); + get_self<implementation::ConfigurationUnit>(unit)->Details(std::move(details)); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResultInformation); + } + + return *unitResult; } Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySet(const ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags) @@ -419,13 +436,18 @@ namespace winrt::Microsoft::Management::Configuration::implementation ApplyConfigurationSetFlags flags, AppInstaller::WinRT::AsyncProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> progress) { - // TODO: Not needed until we have history implemented - UNREFERENCED_PARAMETER(flags); - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - ConfigurationSetApplyProcessor applyProcessor{ configurationSet, m_threadGlobals.GetTelemetryLogger(), m_factory.CreateSetProcessor(configurationSet), std::move(progress) }; - applyProcessor.Process(); + bool consistencyCheckOnly = WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly); + IConfigurationSetProcessor setProcessor; + + if (!consistencyCheckOnly) + { + setProcessor = m_factory.CreateSetProcessor(configurationSet); + } + + ConfigurationSetApplyProcessor applyProcessor{ configurationSet, m_threadGlobals.GetTelemetryLogger(), std::move(setProcessor), std::move(progress) }; + applyProcessor.Process(consistencyCheckOnly); return applyProcessor.Result(); } diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationProcessor.h @@ -64,11 +64,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation const Windows::Foundation::Collections::IVectorView<ConfigurationSet>& configurationSets, bool includeConfigurationHistory); - GetConfigurationSetDetailsResult GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel); - Windows::Foundation::IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel); + GetConfigurationSetDetailsResult GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); + Windows::Foundation::IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); - void GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel); - Windows::Foundation::IAsyncAction GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel); + GetConfigurationUnitDetailsResult GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); + Windows::Foundation::IAsyncOperation<GetConfigurationUnitDetailsResult> GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); ApplyConfigurationSetResult ApplySet(const ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); Windows::Foundation::IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> ApplySetAsync(const ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); @@ -93,10 +93,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation private: GetConfigurationSetDetailsResult GetSetDetailsImpl( const ConfigurationSet& configurationSet, - ConfigurationUnitDetailLevel detailLevel, + ConfigurationUnitDetailFlags detailFlags, AppInstaller::WinRT::AsyncProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> progress = {}); - void GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel); + GetConfigurationUnitDetailsResult GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); ApplyConfigurationSetResult ApplySetImpl( const ConfigurationSet& configurationSet, diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp @@ -46,11 +46,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_progress.Result(*m_result); } - void ConfigurationSetApplyProcessor::Process() + void ConfigurationSetApplyProcessor::Process(bool preProcessOnly) { try { - if (PreProcess()) + if (PreProcess() && !preProcessOnly) { // TODO: Send pending when blocked by another configuration run //SendProgress(ConfigurationSetState::Pending); @@ -62,20 +62,27 @@ namespace winrt::Microsoft::Management::Configuration::implementation SendProgress(ConfigurationSetState::Completed); - m_telemetry.LogConfigProcessingSummaryForApply(*winrt::get_self<implementation::ConfigurationSet>(m_configurationSet), *m_result); + if (!preProcessOnly) + { + m_telemetry.LogConfigProcessingSummaryForApply(*winrt::get_self<implementation::ConfigurationSet>(m_configurationSet), *m_result); + } } catch (...) { - 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)); + 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; } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h @@ -28,7 +28,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigurationSetApplyProcessor(const ConfigurationSet& configurationSet, const TelemetryTraceLogger& telemetry, IConfigurationSetProcessor&& setProcessor, AppInstaller::WinRT::AsyncProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>&& progress); // Processes the apply for the configuration set. - void Process(); + void Process(bool preProcessOnly = false); // Gets the result object. ApplyConfigurationSetResult Result() const; diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp @@ -7,6 +7,32 @@ namespace winrt::Microsoft::Management::Configuration::implementation { + namespace + { + using ValueSet = Windows::Foundation::Collections::ValueSet; + + ValueSet Clone(const ValueSet& source) + { + ValueSet result; + + for (const auto& entry : source) + { + ValueSet child = entry.Value().try_as<ValueSet>(); + + if (child) + { + result.Insert(entry.Key(), Clone(child)); + } + else + { + result.Insert(entry.Key(), entry.Value()); + } + } + + return result; + } + } + ConfigurationUnit::ConfigurationUnit() { GUID instanceIdentifier; @@ -132,4 +158,19 @@ namespace winrt::Microsoft::Management::Configuration::implementation { return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); } + + Configuration::ConfigurationUnit ConfigurationUnit::Copy() + { + auto result = make_self<wil::details::module_count_wrapper<ConfigurationUnit>>(); + + result->m_unitName = m_unitName; + result->m_intent = m_intent; + result->Dependencies(m_dependencies.GetView()); + result->m_directives = Clone(m_directives); + result->m_settings = Clone(m_settings); + result->m_details = m_details; + result->m_schemaVersion = m_schemaVersion; + + return *result; + } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.h b/src/Microsoft.Management.Configuration/ConfigurationUnit.h @@ -47,6 +47,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation hstring SchemaVersion(); void SchemaVersion(const hstring& value); + Configuration::ConfigurationUnit Copy(); + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp @@ -11,6 +11,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_unit = std::move(value); } + void GetConfigurationUnitDetailsResult::Details(IConfigurationUnitProcessorDetails value) + { + m_details = std::move(value); + } + void GetConfigurationUnitDetailsResult::ResultInformation(IConfigurationUnitResultInformation value) { m_resultInformation = std::move(value); @@ -21,6 +26,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation return m_unit; } + IConfigurationUnitProcessorDetails GetConfigurationUnitDetailsResult::Details() + { + return m_details; + } + IConfigurationUnitResultInformation GetConfigurationUnitDetailsResult::ResultInformation() { return m_resultInformation; diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h @@ -13,15 +13,18 @@ namespace winrt::Microsoft::Management::Configuration::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Unit(ConfigurationUnit value); + void Details(IConfigurationUnitProcessorDetails value); void ResultInformation(IConfigurationUnitResultInformation value); #endif ConfigurationUnit Unit(); + IConfigurationUnitProcessorDetails Details(); IConfigurationUnitResultInformation ResultInformation(); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: ConfigurationUnit m_unit = nullptr; + IConfigurationUnitProcessorDetails m_details; IConfigurationUnitResultInformation m_resultInformation; #endif }; diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -35,18 +35,23 @@ namespace Microsoft.Management.Configuration Skipped, }; - // Defines the level of detail probing that is allowed about a configuration unit. + // Defines the type of detail probing that is allowed about a configuration unit. [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationUnitDetailLevel + [flags] + enum ConfigurationUnitDetailFlags { + // For completeness. + None = 0, // Only reads details from local data. - Local, - // Will query the catalog information for details, but will not download any modules. - Catalog, - // Will download modules, but not load them. - Download, - // Will download and load modules for details. - Load, + Local = 0x1, + // Can query the catalog information for details, but will not download any modules. + Catalog = 0x2, + // Can only read data, not write or execute. + ReadOnly = Local | Catalog, + // Can download modules, but not load them. + Download = 0x4, + // Can load modules for details. + Load = 0x8, }; // The source of a result; for instance, the part of the system that generated a failure. @@ -216,6 +221,15 @@ namespace Microsoft.Management.Configuration // The schema version to use for the unit. // Will be set to the schema version when read in, and default to the latest if created manually. String SchemaVersion; + + // Creates a copy of this configuration unit, with the following notes: + // InstanceIdentifier will be a new value + // Identifier will be empty + // Dependencies, Directives, and Settings will by new containers with identical values inside + // Details will be the same value (not a copy, just another reference) + // State, ResultInformation, and ShouldApply will be their default constructed state + // The returned value will be in a writable state + ConfigurationUnit Copy(); } // The change event type that has occurred for a configuration set change. @@ -374,7 +388,7 @@ namespace Microsoft.Management.Configuration interface IConfigurationSetProcessor { // Gets the configuration unit processor details for the given unit. - IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); + IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); // Creates a configuration unit processor for the given unit. // The optional `directivesOverlay` parameter allows for the `ConfigurationProcessor` to alter behavior without needing to change the unit itself. @@ -526,6 +540,9 @@ namespace Microsoft.Management.Configuration // The configuration unit whose details were retrieved. ConfigurationUnit Unit{ get; }; + // The details, if they were able to be acquired successfully. + IConfigurationUnitProcessorDetails Details{ get; }; + // The result of getting the configuration unit details. IConfigurationUnitResultInformation ResultInformation{ get; }; } @@ -540,12 +557,15 @@ namespace Microsoft.Management.Configuration // Flags to control how a configuration set should be applied to the system. [contract(Microsoft.Management.Configuration.Contract, 1)] + [flags] enum ApplyConfigurationSetFlags { None = 0x0, // Forces a new configuration set instance to be recorded when the set being applied matches a previous set's origin. // The default behavior is to assume that the incoming set is an update to the existing set and overwrite it. DoNotOverwriteMatchingOriginSet = 0x1, + // Does not apply the configuration set, instead check the configuration set for internal consistency. + PerformConsistencyCheckOnly = 0x2, }; // The result of applying the settings for a configuration unit. @@ -660,12 +680,12 @@ namespace Microsoft.Management.Configuration Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector<ConfigurationConflict> > CheckForConflictsAsync(Windows.Foundation.Collections.IVectorView<ConfigurationSet> configurationSets, Boolean includeConfigurationHistory); // Gets the details for all configuration units in a set. - GetConfigurationSetDetailsResult GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); - Windows.Foundation.IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); + GetConfigurationSetDetailsResult GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailFlags detailFlags); + Windows.Foundation.IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailFlags detailFlags); // Gets the details for a configuration unit. - void GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); - Windows.Foundation.IAsyncAction GetUnitDetailsAsync(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); + GetConfigurationUnitDetailsResult GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); + Windows.Foundation.IAsyncOperation<GetConfigurationUnitDetailsResult> GetUnitDetailsAsync(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); // Applies the configuration set state. ApplyConfigurationSetResult ApplySet(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs @@ -345,7 +345,7 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands Resources.OperationCompleted, totalUnitsCount); - var detailsTask = processor.GetSetDetailsAsync(set, ConfigurationUnitDetailLevel.Catalog); + var detailsTask = processor.GetSetDetailsAsync(set, ConfigurationUnitDetailFlags.ReadOnly); detailsTask.Progress = detailsProgressOutput.Progress; try