winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ConfigurationFlow.cpp (130375B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ConfigurationFlow.h"
      5 #include "ImportExportFlow.h"
      6 #include "PromptFlow.h"
      7 #include "TableOutput.h"
      8 #include "MSStoreInstallerHandler.h"
      9 #include "Public/ConfigurationSetProcessorFactoryRemoting.h"
     10 #include "ConfigurationCommon.h"
     11 #include "ConfigurationWingetDscModuleUnitValidation.h"
     12 #include "Commands/DscCommandBase.h"
     13 #include <AppInstallerDateTime.h>
     14 #include <AppInstallerDownloader.h>
     15 #include <AppInstallerErrors.h>
     16 #include <AppInstallerRuntime.h>
     17 #include <AppInstallerStrings.h>
     18 #include <winget/ExperimentalFeature.h>
     19 #include <winget/SelfManagement.h>
     20 #include <winrt/Microsoft.Management.Configuration.h>
     21 
     22 using namespace AppInstaller::CLI::Execution;
     23 using namespace winrt::Microsoft::Management::Configuration;
     24 using namespace winrt::Windows::Foundation;
     25 using namespace winrt::Windows::Foundation::Collections;
     26 using namespace winrt::Windows::Storage;
     27 using namespace AppInstaller::Utility::literals;
     28 using namespace AppInstaller::SelfManagement;
     29 
     30 namespace AppInstaller::CLI::Workflow
     31 {
     32 #ifndef AICLI_DISABLE_TEST_HOOKS
     33     IConfigurationSetProcessorFactory s_override_IConfigurationSetProcessorFactory;
     34 
     35     void SetTestConfigurationSetProcessorFactory(IConfigurationSetProcessorFactory factory)
     36     {
     37         s_override_IConfigurationSetProcessorFactory = std::move(factory);
     38     }
     39 #endif
     40 
     41     namespace anon
     42     {
     43         static const AppInstaller::Utility::Version s_MinimumSchemaVersionModuleNameRequiredInType = { "0.3" };
     44 
     45         constexpr std::wstring_view s_Directive_Description = L"description";
     46         constexpr std::wstring_view s_Directive_Module = L"module";
     47         constexpr std::wstring_view s_Directive_AllowPrerelease = L"allowPrerelease";
     48 
     49         constexpr std::wstring_view s_Unit_WinGetPackage = L"WinGetPackage";
     50         constexpr std::wstring_view s_Unit_WinGetSource = L"WinGetSource";
     51 
     52         constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Package";
     53         constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Source";
     54         constexpr std::wstring_view s_UnitType_WinGetUserSettingsFile_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/UserSettingsFile";
     55         constexpr std::wstring_view s_UnitType_WinGetAdminSettings_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/AdminSettings";
     56 
     57         constexpr std::wstring_view s_Module_WinGetClient = L"Microsoft.WinGet.DSC";
     58 
     59         constexpr std::wstring_view s_Setting_WinGetPackage_Id = L"id";
     60         constexpr std::wstring_view s_Setting_WinGetPackage_Source = L"source";
     61         constexpr std::wstring_view s_Setting_WinGetPackage_Version = L"version";
     62 
     63         constexpr std::wstring_view s_Setting_WinGetSource_Name = L"name";
     64         constexpr std::wstring_view s_Setting_WinGetSource_Arg = L"argument";
     65         constexpr std::wstring_view s_Setting_WinGetSource_Type = L"type";
     66 
     67         constexpr std::wstring_view s_Predefined_PowerShell_PackageId = L"Microsoft.PowerShell";
     68         constexpr std::wstring_view s_Predefined_PowerShell_PackageSource = L"winget";
     69 
     70         constexpr std::string_view s_DscPackage_StoreId_Stable = "9NVTPZWRC6KQ";
     71         constexpr std::string_view s_DscPackage_StoreId_Preview = "9PCX3HX4HZ0Z";
     72 
     73         struct PredefinedResourceInfo
     74         {
     75             std::wstring_view UnitType;
     76             bool ElevationRequired = false;
     77 
     78             PredefinedResourceInfo(std::wstring_view unitType) : UnitType(unitType) {}
     79             PredefinedResourceInfo(std::wstring_view unitType, bool elevationRequired) : UnitType(unitType), ElevationRequired(elevationRequired) {}
     80         };
     81 
     82         struct PredefinedResource
     83         {
     84             // RequiredModule could be empty, meaning no required modules needed.
     85             std::wstring_view RequiredModule;
     86 
     87             std::vector<PredefinedResourceInfo> ResourceInfos;
     88         };
     89 
     90         std::vector<PredefinedResource> PredefinedResourcesForExport()
     91         {
     92             return {
     93                 { {}, { { s_UnitType_WinGetUserSettingsFile_DSCv3 }, { s_UnitType_WinGetAdminSettings_DSCv3, true } } },
     94                 { L"Microsoft.Windows.Settings", { { L"Microsoft.Windows.Settings/WindowsSettings", true } } },
     95             };
     96         }
     97 
     98         std::vector<std::wstring_view> PackageSettingsExclusionList()
     99         {
    100             return {
    101                 L"Microsoft.WinGet/",
    102                 L"Microsoft.WinGet.Dev/",
    103                 L"Microsoft.DSC.Debug/",
    104                 L"Microsoft.DSC/",
    105                 L"Microsoft.DSC.Transitional/",
    106                 L"Microsoft.Windows/RebootPending",
    107                 L"Microsoft.Windows/Registry",
    108                 L"Microsoft.Windows/WMI",
    109                 L"Microsoft.Windows/WindowsPowerShell",
    110                 L"Microsoft/OSInfo"
    111             };
    112         };
    113 
    114         Logging::Level ConvertLevel(DiagnosticLevel level)
    115         {
    116             switch (level)
    117             {
    118             case DiagnosticLevel::Verbose: return Logging::Level::Verbose;
    119             case DiagnosticLevel::Informational: return Logging::Level::Info;
    120             case DiagnosticLevel::Warning: return Logging::Level::Warning;
    121             case DiagnosticLevel::Error: return Logging::Level::Error;
    122             case DiagnosticLevel::Critical: return Logging::Level::Crit;
    123             }
    124 
    125             return Logging::Level::Info;
    126         }
    127 
    128         DiagnosticLevel ConvertLevel(Logging::Level level)
    129         {
    130             switch (level)
    131             {
    132             case Logging::Level::Verbose: return DiagnosticLevel::Verbose;
    133             case Logging::Level::Info: return DiagnosticLevel::Informational;
    134             case Logging::Level::Warning: return DiagnosticLevel::Warning;
    135             case Logging::Level::Error: return DiagnosticLevel::Error;
    136             case Logging::Level::Crit: return DiagnosticLevel::Critical;
    137             }
    138 
    139             return DiagnosticLevel::Informational;
    140         }
    141 
    142         Resource::StringId ToResource(ConfigurationUnitIntent intent)
    143         {
    144             switch (intent)
    145             {
    146             case ConfigurationUnitIntent::Assert: return Resource::String::ConfigurationAssert;
    147             case ConfigurationUnitIntent::Inform: return Resource::String::ConfigurationInform;
    148             case ConfigurationUnitIntent::Apply: return Resource::String::ConfigurationApply;
    149             default: return Resource::StringId::Empty();
    150             }
    151         }
    152 
    153         void InstallDscPackage(Execution::Context& context, std::string_view productId, std::unique_ptr<Reporter::AsyncProgressScope>& progressScope)
    154         {
    155             progressScope.reset();
    156 
    157             context.Reporter.Info() << Resource::String::ConfigurationInstallDscPackage << std::endl;
    158 
    159             auto installDscContextPtr = context.CreateSubContext();
    160             Execution::Context& installDscContext = *installDscContextPtr;
    161             auto previousThreadGlobals = installDscContext.SetForCurrentThread();
    162 
    163             Manifest::ManifestInstaller dscInstaller;
    164             dscInstaller.ProductId = productId;
    165 
    166             installDscContext.Add<Execution::Data::Installer>(std::move(dscInstaller));
    167             installDscContext.Args.AddArg(Execution::Args::Type::InstallScope, Manifest::ScopeToString(Manifest::ScopeEnum::User));
    168             installDscContext.Args.AddArg(Execution::Args::Type::Silent);
    169             installDscContext.Args.AddArg(Execution::Args::Type::Force);
    170 
    171             installDscContext << MSStoreInstall;
    172 
    173             if (installDscContext.IsTerminated())
    174             {
    175                 AICLI_LOG(Config, Error, << "Failed to install dsc v3 package: " << productId);
    176                 context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl;
    177                 THROW_WIN32(ERROR_FILE_NOT_FOUND);
    178             }
    179 
    180             progressScope = context.Reporter.BeginAsyncProgress(true);
    181             progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing());
    182         }
    183 
    184         IConfigurationSetProcessorFactory CreateConfigurationSetProcessorFactory(Execution::Context& context)
    185         {
    186 #ifndef AICLI_DISABLE_TEST_HOOKS
    187             // Test could override the entire workflow task, but that may require keeping more in sync than simply setting the factory.
    188             if (s_override_IConfigurationSetProcessorFactory)
    189             {
    190                 return s_override_IConfigurationSetProcessorFactory;
    191             }
    192 #endif
    193 
    194             auto progressScope = context.Reporter.BeginAsyncProgress(true);
    195             progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing());
    196 
    197             // The configuration set must have already been opened to create the proper factory.
    198             THROW_WIN32_IF(ERROR_INVALID_STATE, !context.Contains(Data::ConfigurationContext));
    199             const auto& configurationContext = context.Get<Data::ConfigurationContext>();
    200             THROW_WIN32_IF(ERROR_INVALID_STATE, !configurationContext.Set());
    201 
    202             IConfigurationSetProcessorFactory factory;
    203             ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configurationContext.Set());
    204 
    205             THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, processorEngine == ConfigurationRemoting::ProcessorEngine::Unknown);
    206 
    207             // Since downgrading is not currently supported, only use dynamic if running limited.
    208             if (Runtime::IsRunningWithLimitedToken())
    209             {
    210                 factory = ConfigurationRemoting::CreateDynamicRuntimeFactory(processorEngine);
    211             }
    212             else
    213             {
    214                 factory = ConfigurationRemoting::CreateOutOfProcessFactory(processorEngine);
    215             }
    216 
    217             if (processorEngine == ConfigurationRemoting::ProcessorEngine::PowerShell)
    218             {
    219                 Configuration::SetModulePath(context, factory);
    220             }
    221             else if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3)
    222             {
    223                 auto factoryMap = factory.as<IMap<winrt::hstring, winrt::hstring>>();
    224 
    225                 if (context.Args.Contains(Args::Type::ConfigurationProcessorPath))
    226                 {
    227                     factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePath), Utility::ConvertToUTF16(context.Args.GetArg(Args::Type::ConfigurationProcessorPath)));
    228                 }
    229                 else
    230                 {
    231                     for (;;)
    232                     {
    233                         // Get the next transition for the state machine
    234                         winrt::hstring nextTransition = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FindDscStateMachine));
    235                         AICLI_LOG(Config, Verbose, << "FindDscStateMachine returned " << Utility::ConvertToUTF8(nextTransition));
    236 
    237                         if (nextTransition == L"Found")
    238                         {
    239                             break;
    240                         }
    241                         else if (nextTransition == L"InstallStable")
    242                         {
    243                             AICLI_LOG(Config, Info, << "Installing stable DSC package from store...");
    244                             InstallDscPackage(context, s_DscPackage_StoreId_Stable, progressScope);
    245                         }
    246                         else if (nextTransition == L"InstallPreview")
    247                         {
    248                             AICLI_LOG(Config, Info, << "Installing preview DSC package from store...");
    249                             InstallDscPackage(context, s_DscPackage_StoreId_Preview, progressScope);
    250                         }
    251                         else if (nextTransition == L"NotFound")
    252                         {
    253                             AICLI_LOG(Config, Error, << "Failed to find appropriate dsc v3 package, it must be provided by the user.");
    254                             context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl;
    255                             THROW_WIN32(ERROR_FILE_NOT_FOUND);
    256                         }
    257                         else
    258                         {
    259                             AICLI_LOG(Config, Error, << "FindDscStateMachine returned unknown value `" << Utility::ConvertToUTF8(nextTransition) << "`");
    260                             THROW_HR(E_UNEXPECTED);
    261                         }
    262                     }
    263                 }
    264 
    265                 if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose))
    266                 {
    267                     factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled), L"True");
    268                 }
    269             }
    270 
    271             return factory;
    272         }
    273 
    274         void ConfigureProcessorForUse(Execution::Context& context, ConfigurationProcessor&& processor)
    275         {
    276             // Set the processor to the current level of the logging.
    277             processor.MinimumLevel(anon::ConvertLevel(Logging::Log().GetLevel()));
    278             processor.Caller(L"winget");
    279             // Use same activity as the overall winget command
    280             processor.ActivityIdentifier(*Logging::Telemetry().GetActivityId());
    281             // Apply winget telemetry setting to configuration
    282             processor.GenerateTelemetryEvents(!Settings::User().Get<Settings::Setting::TelemetryDisable>());
    283 
    284             // Route the configuration diagnostics into the context's diagnostics logging
    285             processor.Diagnostics([&context](const winrt::Windows::Foundation::IInspectable&, const IDiagnosticInformation& diagnostics)
    286                 {
    287                     context.GetThreadGlobals().GetDiagnosticLogger().Write(Logging::Channel::Config, anon::ConvertLevel(diagnostics.Level()), Utility::ConvertToUTF8(diagnostics.Message()));
    288                 });
    289 
    290             if (context.Contains(Data::ConfigurationContext))
    291             {
    292                 context.Get<Data::ConfigurationContext>().Processor(std::move(processor));
    293             }
    294             else
    295             {
    296                 ConfigurationContext configurationContext;
    297                 configurationContext.Processor(std::move(processor));
    298 
    299                 context.Add<Data::ConfigurationContext>(std::move(configurationContext));
    300             }
    301         }
    302 
    303         winrt::hstring GetValueSetString(const ValueSet& valueSet, std::wstring_view value)
    304         {
    305             if (valueSet.HasKey(value))
    306             {
    307                 auto object = valueSet.Lookup(value);
    308                 IPropertyValue property = object.try_as<IPropertyValue>();
    309                 if (property && property.Type() == PropertyType::String)
    310                 {
    311                     return property.GetString();
    312                 }
    313             }
    314 
    315             return {};
    316         }
    317 
    318         std::optional<bool> GetValueSetBool(const ValueSet& valueSet, std::wstring_view value)
    319         {
    320             if (valueSet.HasKey(value))
    321             {
    322                 auto object = valueSet.Lookup(value);
    323                 IPropertyValue property = object.try_as<IPropertyValue>();
    324                 if (property && property.Type() == PropertyType::Boolean)
    325                 {
    326                     return property.GetBoolean();
    327                 }
    328             }
    329 
    330             return {};
    331         }
    332 
    333         // Contains the output functions and tracks whether any fields needed to be truncated.
    334         struct OutputHelper
    335         {
    336             OutputHelper(Execution::Context& context) : m_context(context) {}
    337 
    338             size_t ValuesTruncated = 0;
    339 
    340             // Converts a string from the configuration API surface for output.
    341             // All strings coming from the API are external data and not localizable by us.
    342             Utility::LocIndString ConvertForOutput(const std::string& input, size_t maxLines)
    343             {
    344                 bool truncated = false;
    345                 auto lines = Utility::SplitIntoLines(input);
    346 
    347                 if (maxLines == 1 && lines.size() > 1)
    348                 {
    349                     // If the limit was one line, don't allow line breaks but do allow a second line of overflow
    350                     lines.resize(1);
    351                     maxLines = 2;
    352                     truncated = true;
    353                 }
    354 
    355                 if (Utility::LimitOutputLines(lines, GetConsoleWidth(), maxLines))
    356                 {
    357                     truncated = true;
    358                 }
    359 
    360                 if (truncated)
    361                 {
    362                     ++ValuesTruncated;
    363                 }
    364 
    365                 return Utility::LocIndString{ Utility::Join("\n", lines) };
    366             }
    367 
    368             Utility::LocIndString ConvertForOutput(const winrt::hstring& input, size_t maxLines)
    369             {
    370                 return ConvertForOutput(Utility::ConvertToUTF8(input), maxLines);
    371             }
    372 
    373             Utility::LocIndString ConvertIdentifier(const winrt::hstring& input)
    374             {
    375                 return ConvertForOutput(input, 1);
    376             }
    377 
    378             Utility::LocIndString ConvertURI(const winrt::hstring& input)
    379             {
    380                 return ConvertForOutput(input, 1);
    381             }
    382 
    383             Utility::LocIndString ConvertValue(const winrt::hstring& input)
    384             {
    385                 return ConvertForOutput(input, 5);
    386             }
    387 
    388             Utility::LocIndString ConvertDetailsIdentifier(const winrt::hstring& input)
    389             {
    390                 return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 1);
    391             }
    392 
    393             Utility::LocIndString ConvertDetailsURI(const winrt::hstring& input)
    394             {
    395                 return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 1);
    396             }
    397 
    398             Utility::LocIndString ConvertDetailsValue(const winrt::hstring& input)
    399             {
    400                 return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 5);
    401             }
    402 
    403             void OutputValueWithTruncationWarningIfNeeded(const winrt::hstring& input)
    404             {
    405                 size_t truncatedBefore = ValuesTruncated;
    406                 m_context.Reporter.Info() << ConvertValue(input) << '\n';
    407 
    408                 if (ValuesTruncated > truncatedBefore)
    409                 {
    410                     m_context.Reporter.Warn() << Resource::String::ConfigurationWarningValueTruncated << std::endl;
    411                 }
    412             }
    413 
    414             void OutputPropertyValue(const IPropertyValue property)
    415             {
    416                 switch (property.Type())
    417                 {
    418                 case PropertyType::String:
    419                     m_context.Reporter.Info() << ' ';
    420                     OutputValueWithTruncationWarningIfNeeded(property.GetString());
    421                     break;
    422                 case PropertyType::Boolean:
    423                     m_context.Reporter.Info() << ' ' << (property.GetBoolean() ? Utility::LocIndView("true") : Utility::LocIndView("false")) << '\n';
    424                     break;
    425                 case PropertyType::Int64:
    426                     m_context.Reporter.Info() << ' ' << property.GetInt64() << '\n';
    427                     break;
    428                 default:
    429                     m_context.Reporter.Info() << " [Debug:PropertyType="_liv << property.Type() << "]\n"_liv;
    430                     break;
    431                 }
    432             }
    433 
    434             void OutputValueSetAsArray(const ValueSet& valueSetArray, size_t indent)
    435             {
    436                 Utility::LocIndString indentString{ std::string(indent, ' ') };
    437 
    438                 std::vector<std::pair<int, winrt::Windows::Foundation::IInspectable>> arrayValues;
    439                 for (const auto& arrayValue : valueSetArray)
    440                 {
    441                     if (arrayValue.Key() != L"treatAsArray")
    442                     {
    443                         arrayValues.emplace_back(std::make_pair(std::stoi(arrayValue.Key().c_str()), arrayValue.Value()));
    444                     }
    445                 }
    446 
    447                 std::sort(
    448                     arrayValues.begin(),
    449                     arrayValues.end(),
    450                     [](const std::pair<int, winrt::Windows::Foundation::IInspectable>& a, const std::pair<int, winrt::Windows::Foundation::IInspectable>& b)
    451                     {
    452                         return a.first < b.first;
    453                     });
    454 
    455                 for (const auto& arrayValue : arrayValues)
    456                 {
    457                     auto arrayObject = arrayValue.second;
    458                     IPropertyValue arrayProperty = arrayObject.try_as<IPropertyValue>();
    459 
    460                     m_context.Reporter.Info() << indentString << "-";
    461                     if (arrayProperty)
    462                     {
    463                         OutputPropertyValue(arrayProperty);
    464                     }
    465                     else
    466                     {
    467                         ValueSet arraySubset = arrayObject.as<ValueSet>();
    468                         auto size = arraySubset.Size();
    469                         if (size > 0)
    470                         {
    471                             // First one is special.
    472                             auto first = arraySubset.First().Current();
    473                             m_context.Reporter.Info() << ' ' << ConvertIdentifier(first.Key()) << ':';
    474 
    475                             auto object = first.Value();
    476                             IPropertyValue property = object.try_as<IPropertyValue>();
    477                             if (property)
    478                             {
    479                                 OutputPropertyValue(property);
    480                             }
    481                             else
    482                             {
    483                                 // If not an IPropertyValue, it must be a ValueSet
    484                                 ValueSet subset = object.as<ValueSet>();
    485                                 m_context.Reporter.Info() << '\n';
    486                                 OutputValueSet(subset, indent + 4);
    487                             }
    488 
    489                             if (size > 1)
    490                             {
    491                                 arraySubset.Remove(first.Key());
    492                                 OutputValueSet(arraySubset, indent + 2);
    493                                 arraySubset.Insert(first.Key(), first.Value());
    494                             }
    495                         }
    496                     }
    497                 }
    498             }
    499 
    500             void OutputValueSet(const ValueSet& valueSet, size_t indent)
    501             {
    502                 Utility::LocIndString indentString{ std::string(indent, ' ') };
    503 
    504                 for (const auto& value : valueSet)
    505                 {
    506                     m_context.Reporter.Info() << indentString << ConvertIdentifier(value.Key()) << ':';
    507 
    508                     auto object = value.Value();
    509 
    510                     IPropertyValue property = object.try_as<IPropertyValue>();
    511                     if (property)
    512                     {
    513                         OutputPropertyValue(property);
    514                     }
    515                     else
    516                     {
    517                         // If not an IPropertyValue, it must be a ValueSet
    518                         ValueSet subset = object.as<ValueSet>();
    519                         m_context.Reporter.Info() << '\n';
    520                         if (subset.HasKey(L"treatAsArray"))
    521                         {
    522                             OutputValueSetAsArray(subset, indent + 2);
    523                         }
    524                         else
    525                         {
    526                             OutputValueSet(subset, indent + 2);
    527                         }
    528                     }
    529                 }
    530             }
    531 
    532             void OutputConfigurationUnitHeader(const ConfigurationUnit& unit, const winrt::hstring& name)
    533             {
    534                 m_context.Reporter.Info() << ConfigurationUnitEmphasis << ConvertIdentifier(name);
    535 
    536                 if (unit.Environment().Context() == SecurityContext::Elevated)
    537                 {
    538                     // Shield
    539                     m_context.Reporter.Info() << "\xF0\x9F\x9B\xA1 "_liv;
    540                 }
    541 
    542                 winrt::hstring identifier = unit.Identifier();
    543                 if (!identifier.empty())
    544                 {
    545                     m_context.Reporter.Info() << " ["_liv << ConvertIdentifier(identifier) << ']';
    546                 }
    547 
    548                 m_context.Reporter.Info() << '\n';
    549             }
    550 
    551             void OutputConfigurationUnitInformation(const ConfigurationUnit& unit)
    552             {
    553                 IConfigurationUnitProcessorDetails details = unit.Details();
    554                 ValueSet metadata = unit.Metadata();
    555 
    556                 if (details)
    557                 {
    558                     // -- Sample output when IConfigurationUnitProcessorDetails present --
    559                     // UnitType <from details> [Identifier]
    560                     //   UnitDocumentationUri <if present>
    561                     //   Description <from details first, directives second>
    562                     //   "Module": ModuleName "by" Author / Publisher (IsLocal / ModuleSource)
    563                     //     "Signed by": SigningCertificateChain (leaf subject CN)
    564                     //     PublishedModuleUri / ModuleDocumentationUri <if present>
    565                     //     ModuleDescription
    566                     OutputConfigurationUnitHeader(unit, details.UnitType());
    567 
    568                     auto unitDocumentationUri = details.UnitDocumentationUri();
    569                     if (unitDocumentationUri)
    570                     {
    571                         m_context.Reporter.Info() << "  "_liv << ConvertDetailsURI(unitDocumentationUri.DisplayUri()) << '\n';
    572                     }
    573 
    574                     winrt::hstring unitDescriptionFromDetails = details.UnitDescription();
    575                     if (!unitDescriptionFromDetails.empty())
    576                     {
    577                         m_context.Reporter.Info() << "  "_liv << ConvertDetailsValue(unitDescriptionFromDetails) << '\n';
    578                     }
    579 
    580                     auto unitDescriptionFromDirectives = GetValueSetString(metadata, s_Directive_Description);
    581                     if (!unitDescriptionFromDirectives.empty())
    582                     {
    583                         m_context.Reporter.Info() << "  "_liv;
    584                         OutputValueWithTruncationWarningIfNeeded(unitDescriptionFromDirectives);
    585                     }
    586 
    587                     auto author = ConvertDetailsIdentifier(details.Author());
    588                     if (author.empty())
    589                     {
    590                         author = ConvertDetailsIdentifier(details.Publisher());
    591                     }
    592 
    593                     auto moduleName = ConvertDetailsIdentifier(details.ModuleName());
    594                     if (!moduleName.empty())
    595                     {
    596                         if (details.IsLocal())
    597                         {
    598                             m_context.Reporter.Info() << "  "_liv << Resource::String::ConfigurationModuleWithDetails(moduleName, author, Resource::String::ConfigurationLocal) << '\n';
    599                         }
    600                         else
    601                         {
    602                             m_context.Reporter.Info() << "  "_liv << Resource::String::ConfigurationModuleWithDetails(moduleName, author, ConvertDetailsIdentifier(details.ModuleSource())) << '\n';
    603                         }
    604                     }
    605 
    606                     // TODO: Currently the signature information is only for the top files. Maybe each item should be tagged?
    607                     // TODO: Output signing information with additional details (like whether the certificate is trusted). Doing this with the validate command
    608                     //       seems like a good time, as that will also need to do the check in order to inform the user on the validation.
    609                     //       Just saying "Signed By: Foo" is going to lead to a false sense of trust if the signature is valid but not actually trusted.
    610 
    611                     auto moduleUri = details.PublishedModuleUri();
    612                     if (!moduleUri)
    613                     {
    614                         moduleUri = details.ModuleDocumentationUri();
    615                     }
    616                     if (moduleUri)
    617                     {
    618                         m_context.Reporter.Info() << "    "_liv << ConvertDetailsURI(moduleUri.DisplayUri()) << '\n';
    619                     }
    620 
    621                     winrt::hstring moduleDescription = details.ModuleDescription();
    622                     if (!moduleDescription.empty())
    623                     {
    624                         m_context.Reporter.Info() << "    "_liv << ConvertDetailsValue(moduleDescription) << '\n';
    625                     }
    626                 }
    627                 else
    628                 {
    629                     // -- Sample output when no IConfigurationUnitProcessorDetails present --
    630                     // Type <from unit> [identifier]
    631                     //   Description (from directives)
    632                     //   "Module": module <directive>
    633                     OutputConfigurationUnitHeader(unit, unit.Type());
    634 
    635                     auto description = GetValueSetString(metadata, s_Directive_Description);
    636                     if (!description.empty())
    637                     {
    638                         m_context.Reporter.Info() << "  "_liv;
    639                         OutputValueWithTruncationWarningIfNeeded(description);
    640                     }
    641 
    642                     auto module = GetValueSetString(metadata, s_Directive_Module);
    643                     if (!module.empty())
    644                     {
    645                         m_context.Reporter.Info() << "  "_liv << Resource::String::ConfigurationModuleNameOnly(ConvertIdentifier(module)) << '\n';
    646                     }
    647                 }
    648 
    649                 // -- Sample output footer --
    650                 //   Dependencies: dep1, dep2, ...
    651                 //   Settings:
    652                 //     <... settings splat>
    653                 auto dependencies = unit.Dependencies();
    654                 if (dependencies.Size() > 0)
    655                 {
    656                     std::ostringstream allDependencies;
    657                     for (const winrt::hstring& dependency : dependencies)
    658                     {
    659                         allDependencies << ' ' << ConvertIdentifier(dependency);
    660                     }
    661                     m_context.Reporter.Info() << "  "_liv << Resource::String::ConfigurationDependencies(Utility::LocIndString{ std::move(allDependencies).str() }) << '\n';
    662                 }
    663 
    664                 ValueSet settings = unit.Settings();
    665                 if (settings.Size() > 0)
    666                 {
    667                     m_context.Reporter.Info() << "  "_liv << Resource::String::ConfigurationSettings << '\n';
    668                     OutputValueSet(settings, 4);
    669                 }
    670 
    671                 m_context.Reporter.Info() << std::flush;
    672             }
    673 
    674         private:
    675             Execution::Context& m_context;
    676         };
    677 
    678         void OutputConfigurationUnitHeader(Execution::Context& context, const ConfigurationUnit& unit, const winrt::hstring& name)
    679         {
    680             OutputHelper helper{ context };
    681             helper.OutputConfigurationUnitHeader(unit, name);
    682         }
    683 
    684         void LogFailedGetConfigurationUnitDetails(const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation)
    685         {
    686             if (FAILED(resultInformation.ResultCode()))
    687             {
    688                 AICLI_LOG(Config, Error, << "Failed to get unit details for " << Utility::ConvertToUTF8(unit.Type()) << " : 0x" <<
    689                     Logging::SetHRFormat << resultInformation.ResultCode() << '\n' << Utility::ConvertToUTF8(resultInformation.Description()) << '\n' <<
    690                     Utility::ConvertToUTF8(resultInformation.Details()));
    691             }
    692         }
    693 
    694         struct UnitFailedMessageData
    695         {
    696             Utility::LocIndString Message;
    697             bool ShowDescription = true;
    698         };
    699 
    700         // TODO: We may need a detailed result code to enable the internal error to be exposed.
    701         //       Additionally, some of the processor exceptions that generate these errors should be enlightened to produce better, localized descriptions.
    702         UnitFailedMessageData GetUnitFailedData(const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation)
    703         {
    704             int32_t resultCode = resultInformation.ResultCode();
    705 
    706             switch (resultCode)
    707             {
    708             case WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER: return { Resource::String::ConfigurationUnitHasDuplicateIdentifier(Utility::LocIndString{ Utility::ConvertToUTF8(unit.Identifier()) }), false };
    709             case WINGET_CONFIG_ERROR_MISSING_DEPENDENCY: return { Resource::String::ConfigurationUnitHasMissingDependency(Utility::LocIndString{ Utility::ConvertToUTF8(resultInformation.Details()) }), false };
    710             case WINGET_CONFIG_ERROR_ASSERTION_FAILED: return { Resource::String::ConfigurationUnitAssertHadNegativeResult(), false };
    711             case WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED: return { Resource::String::ConfigurationUnitNotFoundInModule(), false };
    712             case WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY: return { Resource::String::ConfigurationUnitNotFound(), false };
    713             case WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES: return { Resource::String::ConfigurationUnitMultipleMatches(), false };
    714             case WINGET_CONFIG_ERROR_UNIT_INVOKE_GET: return { Resource::String::ConfigurationUnitFailedDuringGet(), true };
    715             case WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST: return { Resource::String::ConfigurationUnitFailedDuringTest(), true };
    716             case WINGET_CONFIG_ERROR_UNIT_INVOKE_SET: return { Resource::String::ConfigurationUnitFailedDuringSet(), true };
    717             case WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT: return { Resource::String::ConfigurationUnitModuleConflict(), false };
    718             case WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE: return { Resource::String::ConfigurationUnitModuleImportFailed(), false };
    719             case WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT: return { Resource::String::ConfigurationUnitReturnedInvalidResult(), false };
    720             case WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT: return { Resource::String::ConfigurationUnitSettingConfigRoot(), false };
    721             case WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN: return { Resource::String::ConfigurationUnitImportModuleAdmin(), false };
    722             }
    723 
    724             switch (resultInformation.ResultSource())
    725             {
    726             case ConfigurationUnitResultSource::ConfigurationSet: return { Resource::String::ConfigurationUnitFailedConfigSet(resultCode), true };
    727             case ConfigurationUnitResultSource::Internal: return { Resource::String::ConfigurationUnitFailedInternal(resultCode), true };
    728             case ConfigurationUnitResultSource::Precondition: return { Resource::String::ConfigurationUnitFailedPrecondition(resultCode), true };
    729             case ConfigurationUnitResultSource::SystemState: return { Resource::String::ConfigurationUnitFailedSystemState(resultCode), true };
    730             case ConfigurationUnitResultSource::UnitProcessing: return { Resource::String::ConfigurationUnitFailedUnitProcessing(resultCode), true };
    731             }
    732 
    733             // All other errors use a generic message
    734             return { Resource::String::ConfigurationUnitFailed(resultCode), true };
    735         }
    736 
    737         Utility::LocIndString GetUnitSkippedMessage(const IConfigurationUnitResultInformation& resultInformation)
    738         {
    739             int32_t resultCode = resultInformation.ResultCode();
    740 
    741             switch (resultInformation.ResultCode())
    742             {
    743             case WINGET_CONFIG_ERROR_MANUALLY_SKIPPED: return Resource::String::ConfigurationUnitManuallySkipped();
    744             case WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED: return Resource::String::ConfigurationUnitNotRunDueToDependency();
    745             case WINGET_CONFIG_ERROR_ASSERTION_FAILED: return Resource::String::ConfigurationUnitNotRunDueToFailedAssert();
    746             }
    747 
    748             // If new cases arise and are not handled here, at least have a generic backstop message.
    749             return Resource::String::ConfigurationUnitSkipped(resultCode);
    750         }
    751 
    752         void OutputUnitRunFailure(Context& context, const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation)
    753         {
    754             std::string description = Utility::Trim(Utility::ConvertToUTF8(resultInformation.Description()));
    755 
    756             AICLI_LOG_LARGE_STRING(Config, Error, << "Configuration unit " << Utility::ConvertToUTF8(unit.Type()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] failed with code 0x"
    757                 << Logging::SetHRFormat << resultInformation.ResultCode() << " and error message:\n" << description, Utility::ConvertToUTF8(resultInformation.Details()));
    758 
    759             UnitFailedMessageData messageData = GetUnitFailedData(unit, resultInformation);
    760             auto error = context.Reporter.Error();
    761             error << "  "_liv << messageData.Message << std::endl;
    762 
    763             if (messageData.ShowDescription && !description.empty())
    764             {
    765                 constexpr size_t maximumDescriptionLines = 3;
    766                 size_t consoleWidth = GetConsoleWidth();
    767                 std::vector<std::string> lines = Utility::SplitIntoLines(description, maximumDescriptionLines + 1);
    768                 bool wasLimited = Utility::LimitOutputLines(lines, consoleWidth, maximumDescriptionLines);
    769 
    770                 for (const auto& line : lines)
    771                 {
    772                     error << line << std::endl;
    773                 }
    774 
    775                 if (wasLimited || !resultInformation.Details().empty())
    776                 {
    777                     error << Resource::String::ConfigurationDescriptionWasTruncated << std::endl;
    778                 }
    779             }
    780         }
    781 
    782         // Coordinates an active progress scope and cancellation of the operation.
    783         template<typename OperationT>
    784         struct ProgressCancellationUnification
    785         {
    786             ProgressCancellationUnification(std::unique_ptr<Reporter::AsyncProgressScope>&& progressScope, const OperationT& operation) :
    787                 m_progressScope(std::move(progressScope)), m_operation(operation)
    788             {
    789                 SetCancellationFunction();
    790             }
    791 
    792             void Reset()
    793             {
    794                 m_cancelScope.reset();
    795                 m_progressScope.reset();
    796             }
    797 
    798             Reporter::AsyncProgressScope& Progress() const { return *m_progressScope; }
    799 
    800             void Progress(std::unique_ptr<Reporter::AsyncProgressScope>&& progressScope)
    801             {
    802                 m_cancelScope.reset();
    803                 m_progressScope = std::move(progressScope);
    804                 SetCancellationFunction();
    805             }
    806 
    807             OperationT& Operation() const { return m_operation; }
    808 
    809         private:
    810             void SetCancellationFunction()
    811             {
    812                 if (m_progressScope)
    813                 {
    814                     m_cancelScope = m_progressScope->Callback().SetCancellationFunction([this]() { m_operation.Cancel(); });
    815                 }
    816             }
    817 
    818             std::unique_ptr<Reporter::AsyncProgressScope> m_progressScope;
    819             OperationT m_operation;
    820             IProgressCallback::CancelFunctionRemoval m_cancelScope;
    821         };
    822 
    823         template<typename Operation>
    824         ProgressCancellationUnification<Operation> CreateProgressCancellationUnification(
    825             std::unique_ptr<Reporter::AsyncProgressScope>&& progressScope,
    826             const Operation& operation)
    827         {
    828             return { std::move(progressScope), operation };
    829         }
    830 
    831         // The base type for progress reporting
    832         template<typename ResultType, typename ProgressType>
    833         struct ConfigurationSetProgressOutputBase
    834         {
    835             using Operation = IAsyncOperationWithProgress<ResultType, ProgressType>;
    836 
    837             ConfigurationSetProgressOutputBase(Context& context, const Operation& operation) :
    838                 m_context(context), m_unification({}, operation)
    839             {
    840                 operation.Progress([&](const Operation& operation, const ProgressType& data)
    841                     {
    842                         Progress(operation, data);
    843                     });
    844             }
    845 
    846             virtual void Progress(const Operation& operation, const ProgressType& data) = 0;
    847 
    848         protected:
    849             void MarkCompleted(const ConfigurationUnit& unit)
    850             {
    851                 winrt::guid unitInstance = unit.InstanceIdentifier();
    852                 m_unitsCompleted.insert(unitInstance);
    853             }
    854 
    855             bool UnitHasPreviouslyCompleted(const ConfigurationUnit& unit)
    856             {
    857                 winrt::guid unitInstance = unit.InstanceIdentifier();
    858                 return m_unitsCompleted.count(unitInstance) != 0;
    859             }
    860 
    861             // Sends VT progress to the console
    862             void OutputUnitCompletionProgress()
    863             {
    864                 // TODO: Change progress reporting to enable separation of spinner and VT progress reporting
    865                 //  Preferably we want to be able to have:
    866                 //      1. Spinner with indefinite progress VT before set application begins
    867                 //      2. 1/N VT progress reporting for configuration units while also showing a spinner for the unit itself
    868             }
    869 
    870             void BeginProgress()
    871             {
    872                 m_unification.Progress(m_context.Reporter.BeginAsyncProgress(true));
    873             }
    874 
    875             void EndProgress()
    876             {
    877                 m_unification.Reset();
    878             }
    879 
    880             Context& m_context;
    881 
    882         private:
    883             ProgressCancellationUnification<Operation> m_unification;
    884             std::set<winrt::guid> m_unitsCompleted;
    885         };
    886 
    887         // Helper to handle progress callbacks from ApplyConfigurationSetAsync
    888         struct ApplyConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase<ApplyConfigurationSetResult, ConfigurationSetChangeData>
    889         {
    890             using Operation = ConfigurationSetProgressOutputBase<ApplyConfigurationSetResult, ConfigurationSetChangeData>::Operation;
    891 
    892             ApplyConfigurationSetProgressOutput(Context& context, const Operation& operation) :
    893                 ConfigurationSetProgressOutputBase(context, operation)
    894             {
    895             }
    896 
    897             void Progress(const Operation& operation, const ConfigurationSetChangeData& data) override
    898             {
    899                 auto threadContext = m_context.SetForCurrentThread();
    900 
    901                 if (m_isFirstProgress)
    902                 {
    903                     HandleUnreportedProgress(operation.GetResults());
    904                 }
    905 
    906                 switch (data.Change())
    907                 {
    908                 case ConfigurationSetChangeEventType::SetStateChanged:
    909                 {
    910                     switch (data.SetState())
    911                     {
    912                     case ConfigurationSetState::Pending:
    913                         m_context.Reporter.Info() << Resource::String::ConfigurationWaitingOnAnother << std::endl;
    914                         BeginProgress();
    915                         break;
    916                     case ConfigurationSetState::InProgress:
    917                         EndProgress();
    918                         break;
    919                     case ConfigurationSetState::Completed:
    920                         EndProgress();
    921                         break;
    922                     }
    923                 }
    924                     break;
    925                 case ConfigurationSetChangeEventType::UnitStateChanged:
    926                     HandleUnitProgress(data.Unit(), data.UnitState(), data.ResultInformation());
    927                     break;
    928                 }
    929             }
    930 
    931             // If no progress has been reported, this function will report the given results
    932             void HandleUnreportedProgress(const ApplyConfigurationSetResult& result)
    933             {
    934                 if (m_isFirstProgress)
    935                 {
    936                     m_isFirstProgress = false;
    937 
    938                     for (const ApplyConfigurationUnitResult& unitResult : result.UnitResults())
    939                     {
    940                         HandleUnitProgress(unitResult.Unit(), unitResult.State(), unitResult.ResultInformation());
    941                     }
    942                 }
    943             }
    944 
    945         private:
    946             void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationUnitState state, const IConfigurationUnitResultInformation& resultInformation)
    947             {
    948                 if (UnitHasPreviouslyCompleted(unit))
    949                 {
    950                     return;
    951                 }
    952 
    953                 switch (state)
    954                 {
    955                 case ConfigurationUnitState::Pending:
    956                     // The unreported progress handler may send pending units, just ignore them
    957                     break;
    958                 case ConfigurationUnitState::InProgress:
    959                     OutputUnitInProgressIfNeeded(unit);
    960                     BeginProgress();
    961                     break;
    962                 case ConfigurationUnitState::Completed:
    963                     OutputUnitInProgressIfNeeded(unit);
    964                     EndProgress();
    965                     if (SUCCEEDED(resultInformation.ResultCode()))
    966                     {
    967                         m_context.Reporter.Info() << "  "_liv << Resource::String::ConfigurationUnitSuccessfullyApplied << std::endl;
    968                     }
    969                     else
    970                     {
    971                         OutputUnitRunFailure(m_context, unit, resultInformation);
    972                     }
    973                     MarkCompleted(unit);
    974                     OutputUnitCompletionProgress();
    975                     break;
    976                 case ConfigurationUnitState::Skipped:
    977                     OutputUnitInProgressIfNeeded(unit);
    978                     AICLI_LOG(Config, Warning, << "Configuration unit " << Utility::ConvertToUTF8(unit.Type()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] was skipped with code 0x"
    979                         << Logging::SetHRFormat << resultInformation.ResultCode());
    980                     m_context.Reporter.Warn() << "  "_liv << GetUnitSkippedMessage(resultInformation) << std::endl;
    981                     MarkCompleted(unit);
    982                     OutputUnitCompletionProgress();
    983                     break;
    984                 }
    985             }
    986 
    987             void OutputUnitInProgressIfNeeded(const ConfigurationUnit& unit)
    988             {
    989                 winrt::guid unitInstance = unit.InstanceIdentifier();
    990                 if (m_unitsSeen.count(unitInstance) == 0)
    991                 {
    992                     m_unitsSeen.insert(unitInstance);
    993 
    994                     OutputConfigurationUnitHeader(m_context, unit, unit.Details() ? unit.Details().UnitType() : unit.Type());
    995                 }
    996             }
    997 
    998             std::set<winrt::guid> m_unitsSeen;
    999             bool m_isFirstProgress = true;
   1000         };
   1001 
   1002         // Helper to handle progress callbacks from TestConfigurationSetAsync
   1003         struct TestConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase<TestConfigurationSetResult, TestConfigurationUnitResult>
   1004         {
   1005             using Operation = ConfigurationSetProgressOutputBase<TestConfigurationSetResult, TestConfigurationUnitResult>::Operation;
   1006 
   1007             TestConfigurationSetProgressOutput(Context& context, const Operation& operation) :
   1008                 ConfigurationSetProgressOutputBase(context, operation)
   1009             {
   1010                 // Start the spinner for the first unit being tested since we only receive completions
   1011                 BeginProgress();
   1012             }
   1013 
   1014             void Progress(const Operation& operation, const TestConfigurationUnitResult& data) override
   1015             {
   1016                 auto threadContext = m_context.SetForCurrentThread();
   1017 
   1018                 if (m_isFirstProgress)
   1019                 {
   1020                     HandleUnreportedProgress(operation.GetResults());
   1021                 }
   1022 
   1023                 HandleUnitProgress(data.Unit(), data.TestResult(), data.ResultInformation());
   1024             }
   1025 
   1026             // If no progress has been reported, this function will report the given results
   1027             void HandleUnreportedProgress(const TestConfigurationSetResult& result)
   1028             {
   1029                 if (m_isFirstProgress)
   1030                 {
   1031                     m_isFirstProgress = false;
   1032 
   1033                     for (const TestConfigurationUnitResult& unitResult : result.UnitResults())
   1034                     {
   1035                         HandleUnitProgress(unitResult.Unit(), unitResult.TestResult(), unitResult.ResultInformation());
   1036                     }
   1037                 }
   1038             }
   1039 
   1040         private:
   1041             void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationTestResult testResult, const IConfigurationUnitResultInformation& resultInformation)
   1042             {
   1043                 if (UnitHasPreviouslyCompleted(unit))
   1044                 {
   1045                     return;
   1046                 }
   1047 
   1048                 EndProgress();
   1049 
   1050                 OutputConfigurationUnitHeader(m_context, unit, unit.Details() ? unit.Details().UnitType() : unit.Type());
   1051 
   1052                 switch (testResult)
   1053                 {
   1054                 case ConfigurationTestResult::Failed:
   1055                     OutputUnitRunFailure(m_context, unit, resultInformation);
   1056                     break;
   1057                 case ConfigurationTestResult::Negative:
   1058                     m_context.Reporter.Warn() << "  "_liv << Resource::String::ConfigurationNotInDesiredState << std::endl;
   1059                     break;
   1060                 case ConfigurationTestResult::NotRun:
   1061                     m_context.Reporter.Warn() << "  "_liv << Resource::String::ConfigurationNoTestRun << std::endl;
   1062                     break;
   1063                 case ConfigurationTestResult::Positive:
   1064                     m_context.Reporter.Info() << "  "_liv << Resource::String::ConfigurationInDesiredState << std::endl;
   1065                     break;
   1066                 default: // ConfigurationTestResult::Unknown
   1067                     m_context.Reporter.Error() << "  "_liv << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(testResult)) << std::endl;
   1068                     break;
   1069                 }
   1070 
   1071                 MarkCompleted(unit);
   1072                 OutputUnitCompletionProgress();
   1073                 BeginProgress();
   1074             }
   1075 
   1076             bool m_isFirstProgress = true;
   1077         };
   1078 
   1079         std::string GetNormalizedIdentifier(const winrt::hstring& identifier)
   1080         {
   1081             return Utility::FoldCase(Utility::NormalizedString{ identifier });
   1082         }
   1083 
   1084         // Get unit validation order. Make sure dependency units are before units depending on them.
   1085         std::vector<uint32_t> GetConfigurationSetUnitValidationOrder(winrt::Windows::Foundation::Collections::IVectorView<ConfigurationUnit> units)
   1086         {
   1087             // Create id to index map for easier processing.
   1088             std::map<std::string, uint32_t> idToUnitIndex;
   1089             for (uint32_t i = 0; i < units.Size(); ++i)
   1090             {
   1091                 auto id = GetNormalizedIdentifier(units.GetAt(i).Identifier());
   1092                 if (!id.empty())
   1093                 {
   1094                     idToUnitIndex.emplace(std::move(id), i);
   1095                 }
   1096             }
   1097 
   1098             // We do not need to worry about duplicate id, missing dependency or loops
   1099             // since dependency integrity is already validated in earlier semantic checks.
   1100 
   1101             std::vector<uint32_t> validationOrder;
   1102 
   1103             std::function<void(const ConfigurationUnit&, uint32_t)> addUnitToValidationOrder =
   1104                 [&](const ConfigurationUnit& unit, uint32_t index)
   1105                 {
   1106                     if (std::find(validationOrder.begin(), validationOrder.end(), index) == validationOrder.end())
   1107                     {
   1108                         for (auto const& dependencyId : unit.Dependencies())
   1109                         {
   1110                             auto dependencyIndex = idToUnitIndex.find(GetNormalizedIdentifier(dependencyId))->second;
   1111                             addUnitToValidationOrder(units.GetAt(dependencyIndex), dependencyIndex);
   1112                         }
   1113                         validationOrder.emplace_back(index);
   1114                     }
   1115                 };
   1116 
   1117             for (uint32_t i = 0; i < units.Size(); ++i)
   1118             {
   1119                 addUnitToValidationOrder(units.GetAt(i), i);
   1120             }
   1121 
   1122             THROW_HR_IF(E_UNEXPECTED, units.Size() != validationOrder.size());
   1123 
   1124             return validationOrder;
   1125         }
   1126 
   1127         void SetNameAndOrigin(ConfigurationSet& set, std::filesystem::path& absolutePath)
   1128         {
   1129             // TODO: Consider how to properly determine a good value for name and origin.
   1130             set.Name(absolutePath.filename().wstring());
   1131             set.Origin(absolutePath.parent_path().wstring());
   1132             set.Path(absolutePath.wstring());
   1133         }
   1134 
   1135         void OpenConfigurationSet(Execution::Context& context, const std::string& argPath, bool allowRemote)
   1136         {
   1137             auto progressScope = context.Reporter.BeginAsyncProgress(true);
   1138             progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationReadingConfigFile());
   1139 
   1140             std::wstring argPathWide = Utility::ConvertToUTF16(argPath);
   1141             bool isRemote = Utility::IsUrlRemote(argPath);
   1142             std::filesystem::path absolutePath;
   1143             Streams::IInputStream inputStream = nullptr;
   1144 
   1145             if (isRemote)
   1146             {
   1147                 if (!allowRemote)
   1148                 {
   1149                     AICLI_LOG(Config, Error, << "Remote files are not supported");
   1150                     AICLI_TERMINATE_CONTEXT(ERROR_NOT_SUPPORTED);
   1151                 }
   1152 
   1153                 std::ostringstream stringStream;
   1154                 ProgressCallback emptyCallback;
   1155                 Utility::DownloadToStream(argPath, stringStream, Utility::DownloadType::ConfigurationFile, emptyCallback);
   1156 
   1157                 auto strContent = stringStream.str();
   1158                 std::vector<BYTE> byteContent{ strContent.begin(), strContent.end() };
   1159 
   1160                 Streams::InMemoryRandomAccessStream memoryStream;
   1161                 Streams::DataWriter streamWriter{ memoryStream };
   1162                 streamWriter.WriteBytes(byteContent);
   1163                 streamWriter.StoreAsync().get();
   1164                 streamWriter.DetachStream();
   1165                 memoryStream.Seek(0);
   1166                 inputStream = memoryStream;
   1167             }
   1168             else
   1169             {
   1170                 absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPathWide });
   1171                 auto openAction = Streams::FileRandomAccessStream::OpenAsync(absolutePath.wstring(), FileAccessMode::Read);
   1172                 auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); });
   1173                 inputStream = openAction.get();
   1174             }
   1175 
   1176             OpenConfigurationSetResult openResult = nullptr;
   1177             {
   1178                 auto openAction = context.Get<Data::ConfigurationContext>().Processor().OpenConfigurationSetAsync(inputStream);
   1179                 auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); });
   1180                 openResult = openAction.get();
   1181             }
   1182 
   1183             progressScope.reset();
   1184 
   1185             if (FAILED_LOG(static_cast<HRESULT>(openResult.ResultCode().value)))
   1186             {
   1187                 AICLI_LOG(Config, Error, << "Failed to open configuration set at " << (isRemote ? argPath : absolutePath.u8string()) << " with error 0x" << Logging::SetHRFormat << static_cast<HRESULT>(openResult.ResultCode().value));
   1188 
   1189                 switch (openResult.ResultCode())
   1190                 {
   1191                 case WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE:
   1192                     context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidType(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl;
   1193                     break;
   1194                 case WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE:
   1195                     context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidValue(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }, Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl;
   1196                     break;
   1197                 case WINGET_CONFIG_ERROR_MISSING_FIELD:
   1198                     context.Reporter.Error() << Resource::String::ConfigurationFieldMissing(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl;
   1199                     break;
   1200                 case WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION:
   1201                     context.Reporter.Error() << Resource::String::ConfigurationFileVersionUnknown(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl;
   1202                     break;
   1203                 case WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE:
   1204                 case WINGET_CONFIG_ERROR_INVALID_YAML:
   1205                 default:
   1206                     context.Reporter.Error() << Resource::String::ConfigurationFileInvalidYAML << std::endl;
   1207                     break;
   1208                 }
   1209 
   1210                 if (openResult.Line() != 0)
   1211                 {
   1212                     context.Reporter.Error() << Resource::String::SeeLineAndColumn(openResult.Line(), openResult.Column()) << std::endl;
   1213                 }
   1214 
   1215                 AICLI_TERMINATE_CONTEXT(openResult.ResultCode());
   1216             }
   1217 
   1218             ConfigurationSet result = openResult.Set();
   1219 
   1220             // Fill out the information about the set based on it coming from a file.
   1221             if (isRemote)
   1222             {
   1223                 result.Name(Utility::GetFileNameFromURI(argPath).wstring());
   1224                 result.Origin(argPathWide);
   1225                 // Do not set path. This means ${WinGetConfigRoot} not supported in remote configs.
   1226             }
   1227             else
   1228             {
   1229                 SetNameAndOrigin(result, absolutePath);
   1230             }
   1231 
   1232             context.Get<Data::ConfigurationContext>().Set(result);
   1233         }
   1234 
   1235         ConfigurationUnit CreateConfigurationUnitFromModuleResource(std::string_view moduleName, std::string_view resourceName, std::string_view descriptionResourceName, const Utility::Version& schemaVersion)
   1236         {
   1237             std::wstring moduleNameWide = Utility::ConvertToUTF16(moduleName);
   1238             std::wstring resourceNameWide = Utility::ConvertToUTF16(resourceName);
   1239 
   1240             ConfigurationUnit unit;
   1241             unit.Type(schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType ? moduleNameWide + L'/' + resourceNameWide : resourceNameWide);
   1242             unit.Identifier(unit.Type() + L'_' + Utility::ConvertToUTF16(Utility::GetRandomString()));
   1243 
   1244             ValueSet directives;
   1245             directives.Insert(s_Directive_Module, PropertyValue::CreateString(moduleNameWide));
   1246 
   1247             Utility::LocIndString description;
   1248             if (!descriptionResourceName.empty())
   1249             {
   1250                 description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ descriptionResourceName });
   1251             }
   1252             else
   1253             {
   1254                 description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ resourceName });
   1255             }
   1256 
   1257             directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get())));
   1258             unit.Metadata(directives);
   1259 
   1260             return unit;
   1261         }
   1262 
   1263         ConfigurationUnit CreateConfigurationUnitFromUnitType(std::wstring_view unitType, std::string_view descriptionResourceName = "")
   1264         {
   1265             ConfigurationUnit unit;
   1266             unit.Type(unitType);
   1267             unit.Identifier(unit.Type() + L'_' + Utility::ConvertToUTF16(Utility::GetRandomString()));
   1268 
   1269             ValueSet directives;
   1270             Utility::LocIndString description;
   1271             if (!descriptionResourceName.empty())
   1272             {
   1273                 description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ descriptionResourceName });
   1274             }
   1275             else
   1276             {
   1277                 description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ Utility::ConvertToUTF8(unitType) });
   1278             }
   1279 
   1280             directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get())));
   1281             unit.Metadata(directives);
   1282 
   1283             return unit;
   1284         }
   1285 
   1286         ConfigurationUnit CreatePowerShellPackageUnit()
   1287         {
   1288             ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(s_UnitType_WinGetPackage_DSCv3, "Microsoft.PowerShell");
   1289 
   1290             ValueSet settings;
   1291             settings.Insert(s_Setting_WinGetPackage_Id, PropertyValue::CreateString(s_Predefined_PowerShell_PackageId));
   1292             settings.Insert(s_Setting_WinGetPackage_Source, PropertyValue::CreateString(s_Predefined_PowerShell_PackageSource));
   1293             unit.Settings(settings);
   1294 
   1295             return unit;
   1296         }
   1297 
   1298         ValueSet CreateValueSetFromStringVector(const std::vector<std::wstring>& values)
   1299         {
   1300             ValueSet result;
   1301             size_t index = 0;
   1302 
   1303             for (const auto& value : values)
   1304             {
   1305                 std::wostringstream strstr;
   1306                 strstr << index++;
   1307                 result.Insert(strstr.str(), PropertyValue::CreateString(value));
   1308             }
   1309 
   1310             result.Insert(L"treatAsArray", PropertyValue::CreateBoolean(true));
   1311             return result;
   1312         }
   1313 
   1314         // TODO: This is a workaround unit to ensure v2 dsc resource modules. Move to dsc v3 resource when available.
   1315         ConfigurationUnit CreateRequiredModuleUnit(std::wstring_view moduleName, const ConfigurationUnit& dependentUnit)
   1316         {
   1317             std::wstring moduleNameString{ moduleName };
   1318 
   1319             ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(L"Microsoft.DSC.Transitional/RunCommandOnSet", Utility::ConvertToUTF8(moduleName));
   1320 
   1321             ValueSet settings;
   1322             settings.Insert(L"executable", PropertyValue::CreateString(L"pwsh"));
   1323             std::vector<std::wstring> arguments =
   1324             {
   1325                 L"-NoProfile",
   1326                 L"-NoLogo",
   1327                 L"-Command",
   1328                 L"if (-not (Get-Module -ListAvailable -Name " + moduleNameString + L")) { Install-Module -Name " + moduleNameString + L" -Confirm:$False -Force -AllowPrerelease -AllowClobber }"
   1329             };
   1330             settings.Insert(L"arguments", CreateValueSetFromStringVector(arguments));
   1331             unit.Settings(settings);
   1332 
   1333             unit.Dependencies().Append(dependentUnit.Identifier());
   1334 
   1335             return unit;
   1336         }
   1337 
   1338         std::wstring GetWinGetSourceUnitType(const ConfigurationContext& configContext)
   1339         {
   1340             Utility::Version schemaVersion = { Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) };
   1341             ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configContext.Set());
   1342 
   1343             if (schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType)
   1344             {
   1345                 if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3)
   1346                 {
   1347                     return std::wstring{ s_UnitType_WinGetSource_DSCv3 };
   1348                 }
   1349                 else
   1350                 {
   1351                     return std::wstring{ s_Module_WinGetClient } + L'/' + std::wstring{ s_Unit_WinGetSource };
   1352                 }
   1353             }
   1354             else
   1355             {
   1356                 return std::wstring{ s_Unit_WinGetSource };
   1357             }
   1358         }
   1359 
   1360         ConfigurationUnit CreateWinGetSourceUnit(const PackageCollection::Source& source, std::wstring_view unitType)
   1361         {
   1362             std::string sourceUnitId = source.Details.Name + '_' + source.Details.Type;
   1363             std::wstring sourceUnitIdWide = Utility::ConvertToUTF16(sourceUnitId);
   1364 
   1365             ConfigurationUnit unit;
   1366             unit.Type(unitType);
   1367             unit.Identifier(sourceUnitIdWide);
   1368             unit.Intent(ConfigurationUnitIntent::Apply);
   1369 
   1370             auto description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ sourceUnitId });
   1371 
   1372             ValueSet directives;
   1373             directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get())));
   1374             unit.Metadata(directives);
   1375 
   1376             ValueSet settings;
   1377             settings.Insert(s_Setting_WinGetSource_Name, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Name)));
   1378             settings.Insert(s_Setting_WinGetSource_Arg, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Arg)));
   1379             settings.Insert(s_Setting_WinGetSource_Type, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Type)));
   1380             unit.Settings(settings);
   1381 
   1382             unit.Environment().Context(SecurityContext::Elevated);
   1383 
   1384             return unit;
   1385         }
   1386 
   1387         std::wstring GetWinGetPackageUnitType(const ConfigurationContext& configContext)
   1388         {
   1389             Utility::Version schemaVersion = { Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) };
   1390             ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configContext.Set());
   1391 
   1392             if (schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType)
   1393             {
   1394                 if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3)
   1395                 {
   1396                     return std::wstring{ s_UnitType_WinGetPackage_DSCv3 };
   1397                 }
   1398                 else
   1399                 {
   1400                     return std::wstring{ s_Module_WinGetClient } + L'/' + std::wstring{ s_Unit_WinGetPackage };
   1401                 }
   1402             }
   1403             else
   1404             {
   1405                 return std::wstring{ s_Unit_WinGetPackage };
   1406             }
   1407         }
   1408 
   1409         ConfigurationUnit CreateWinGetPackageUnit(const PackageCollection::Package& package, const PackageCollection::Source& source, bool includeVersion, const std::optional<ConfigurationUnit>& dependentUnit, std::wstring_view unitType)
   1410         {
   1411             std::wstring packageIdWide = Utility::ConvertToUTF16(package.Id);
   1412             std::wstring sourceNameWide = Utility::ConvertToUTF16(source.Details.Name);
   1413 
   1414             ConfigurationUnit unit;
   1415             unit.Type(unitType);
   1416             unit.Identifier(sourceNameWide + L'_' + packageIdWide);
   1417             unit.Intent(ConfigurationUnitIntent::Apply);
   1418 
   1419             auto description = Resource::String::ConfigureExportUnitInstallDescription(Utility::LocIndView{ package.Id });
   1420 
   1421             ValueSet directives;
   1422             directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get())));
   1423             unit.Metadata(directives);
   1424 
   1425             ValueSet settings;
   1426             settings.Insert(s_Setting_WinGetPackage_Id, PropertyValue::CreateString(packageIdWide));
   1427             settings.Insert(s_Setting_WinGetPackage_Source, PropertyValue::CreateString(sourceNameWide));
   1428             if (includeVersion)
   1429             {
   1430                 settings.Insert(s_Setting_WinGetPackage_Version, PropertyValue::CreateString(Utility::ConvertToUTF16(package.VersionAndChannel.GetVersion().ToString())));
   1431             }
   1432             unit.Settings(settings);
   1433 
   1434             // TODO: We may consider setting security environment based on installer elevation requirements?
   1435 
   1436             // Add dependency if needed.
   1437             if (dependentUnit.has_value())
   1438             {
   1439                 auto dependencies = winrt::single_threaded_vector<winrt::hstring>();
   1440                 dependencies.Append(dependentUnit.value().Identifier());
   1441                 unit.Dependencies(std::move(dependencies));
   1442             }
   1443 
   1444             return unit;
   1445         }
   1446 
   1447         ApplyConfigurationUnitResult ApplyUnit(Execution::Context& context, ConfigurationUnit& unit)
   1448         {
   1449             unit.Intent(ConfigurationUnitIntent::Apply);
   1450 
   1451             auto progressScope = context.Reporter.BeginAsyncProgress(true);
   1452 
   1453             progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationApplyingUnit());
   1454 
   1455             ApplyConfigurationUnitResult applyResult = nullptr;
   1456             {
   1457                 auto applyAction = context.Get<Data::ConfigurationContext>().Processor().ApplyUnitAsync(unit);
   1458                 auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { applyAction.Cancel(); });
   1459                 applyResult = applyAction.get();
   1460             }
   1461 
   1462             progressScope.reset();
   1463             return applyResult;
   1464         }
   1465 
   1466         GetConfigurationUnitSettingsResult GetUnitSettings(Execution::Context& context, ConfigurationUnit& unit)
   1467         {
   1468             // This assumes there are no required properties for Get, but for example WinGetPackage requires the Id.
   1469             // It is obviously wrong and will be wrong until Export is implemented for DSC v2 and a proper way to inform
   1470             // about input to winget configure export is implemented. Drink the kool-aid and transcend.
   1471             unit.Intent(ConfigurationUnitIntent::Inform);
   1472 
   1473             auto progressScope = context.Reporter.BeginAsyncProgress(true);
   1474 
   1475             progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationGettingResourceSettings());
   1476 
   1477             GetConfigurationUnitSettingsResult getResult = nullptr;
   1478             {
   1479                 auto getAction = context.Get<Data::ConfigurationContext>().Processor().GetUnitSettingsAsync(unit);
   1480                 auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { getAction.Cancel(); });
   1481                 getResult = getAction.get();
   1482             }
   1483 
   1484             progressScope.reset();
   1485             return getResult;
   1486         }
   1487 
   1488         GetAllConfigurationUnitsResult GetAllUnits(Execution::Context& context, ConfigurationUnit& unit)
   1489         {
   1490             unit.Intent(ConfigurationUnitIntent::Inform);
   1491 
   1492             auto progressScope = context.Reporter.BeginAsyncProgress(true);
   1493 
   1494             progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationExportingUnit());
   1495 
   1496             GetAllConfigurationUnitsResult getResult = nullptr;
   1497             {
   1498                 auto getAction = context.Get<Data::ConfigurationContext>().Processor().GetAllUnitsAsync(unit);
   1499                 auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { getAction.Cancel(); });
   1500                 getResult = getAction.get();
   1501             }
   1502 
   1503             progressScope.reset();
   1504             return getResult;
   1505         }
   1506 
   1507         std::vector<ConfigurationUnit> ExportUnit(Execution::Context& context, ConfigurationUnit& unit, bool throwOnFailure = false)
   1508         {
   1509             std::vector<ConfigurationUnit> result;
   1510 
   1511             context.Reporter.Info() << Resource::String::ConfigurationExportUnitStart(Utility::LocIndView{ Utility::ConvertToUTF8(unit.Type()) }) << std::endl;
   1512 
   1513             // Try export first
   1514             auto exportResult = GetAllUnits(context, unit);
   1515             auto exportResultCode = exportResult.ResultInformation().ResultCode();
   1516             if (SUCCEEDED(exportResultCode))
   1517             {
   1518                 for (auto resultUnit : exportResult.Units())
   1519                 {
   1520                     result.emplace_back(std::move(resultUnit));
   1521                 }
   1522             }
   1523             else
   1524             {
   1525                 AICLI_LOG(Config, Warning, << "Failed GetAllUnits. Will try GetUnitSettings.");
   1526                 LogFailedGetConfigurationUnitDetails(unit, exportResult.ResultInformation());
   1527 
   1528                 // Try GetUnitSettings if export failed.
   1529                 auto getResult = GetUnitSettings(context, unit);
   1530                 auto getResultCode = getResult.ResultInformation().ResultCode();
   1531                 if (getResultCode == WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY)
   1532                 {
   1533                     // Retry if it fails with not found in the case the module is a pre-released one.
   1534                     AICLI_LOG(Config, Info, << "Failed GetUnitSettings because module not found. Will try allow prerelease.");
   1535                     auto directives = unit.Metadata();
   1536                     directives.Insert(s_Directive_AllowPrerelease, PropertyValue::CreateBoolean(true));
   1537                     unit.Metadata(directives);
   1538 
   1539                     getResult = GetUnitSettings(context, unit);
   1540                 }
   1541 
   1542                 if (FAILED(getResult.ResultInformation().ResultCode()))
   1543                 {
   1544                     AICLI_LOG(Config, Error, << "Failed Get Unit Settings");
   1545                     LogFailedGetConfigurationUnitDetails(unit, getResult.ResultInformation());
   1546 
   1547                     if (throwOnFailure)
   1548                     {
   1549                         context.Reporter.Error() << Resource::String::ConfigurationExportUnitFailed << std::endl;
   1550                         OutputUnitRunFailure(context, unit, getResult.ResultInformation());
   1551                         THROW_HR(WINGET_CONFIG_ERROR_GET_FAILED);
   1552                     }
   1553                     else
   1554                     {
   1555                         context.Reporter.Warn() << Resource::String::ConfigurationExportUnitFailed << std::endl;
   1556                     }
   1557                 }
   1558                 else
   1559                 {
   1560                     unit.Settings(getResult.Settings());
   1561                     result.emplace_back(unit);
   1562                 }
   1563             }
   1564 
   1565             return result;
   1566         }
   1567 
   1568         void AddDependentUnit(std::vector<ConfigurationUnit>& units, const ConfigurationUnit& dependentUnit)
   1569         {
   1570             for (auto& unit : units)
   1571             {
   1572                 unit.Dependencies().Append(dependentUnit.Identifier());
   1573             }
   1574         }
   1575 
   1576         void AddElevatedEnvironment(std::vector<ConfigurationUnit>& units)
   1577         {
   1578             for (auto& unit : units)
   1579             {
   1580                 unit.Environment().Context(SecurityContext::Elevated);
   1581             }
   1582         }
   1583 
   1584         std::vector<IConfigurationUnitProcessorDetails> GetAllUnitProcessors(Execution::Context& context)
   1585         {
   1586             ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   1587             std::vector<IConfigurationUnitProcessorDetails> result;
   1588 
   1589             // Only supported by dsc v3 processor.
   1590             if (ConfigurationRemoting::ProcessorEngine::DSCv3 == ConfigurationRemoting::DetermineProcessorEngine(configContext.Set()))
   1591             {
   1592                 auto progressScope = context.Reporter.BeginAsyncProgress(true);
   1593 
   1594                 progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationGettingUnitProcessors());
   1595 
   1596                 {
   1597                     FindUnitProcessorsOptions findOptions;
   1598                     findOptions.UnitDetailFlags(ConfigurationUnitDetailFlags::Local);
   1599                     auto findAction = context.Get<Data::ConfigurationContext>().Processor().FindUnitProcessorsAsync(findOptions);
   1600                     auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { findAction.Cancel(); });
   1601                     for (auto unitProcessor : findAction.get())
   1602                     {
   1603                         result.emplace_back(std::move(unitProcessor));
   1604                     }
   1605                 }
   1606 
   1607                 progressScope.reset();
   1608             }
   1609 
   1610             return result;
   1611         }
   1612 
   1613         void ExportPredefinedResources(Execution::Context& context)
   1614         {
   1615             ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   1616 
   1617             // PowerShell package needs to be present for certain predefined modules to work.
   1618             ConfigurationUnit powerShellPackageUnit = CreatePowerShellPackageUnit();
   1619             configContext.Set().Units().Append(powerShellPackageUnit);
   1620 
   1621             // Apply the unit to make sure it's on the system.
   1622             context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ "Microsoft PowerShell Package" }) << std::endl;
   1623             auto applyPowerShellResult = ApplyUnit(context, powerShellPackageUnit);
   1624             if (FAILED(applyPowerShellResult.ResultInformation().ResultCode()))
   1625             {
   1626                 AICLI_LOG(Config, Warning, << "Failed to ensure module. [Microsoft PowerShell Package] Related settings may not be exported.");
   1627                 LogFailedGetConfigurationUnitDetails(powerShellPackageUnit, applyPowerShellResult.ResultInformation());
   1628                 context.Reporter.Warn() << Resource::String::ConfigurationExportInstallRequiredModuleFailed << std::endl;
   1629             }
   1630 
   1631             for (const auto& resources : PredefinedResourcesForExport())
   1632             {
   1633                 std::optional<ConfigurationUnit> requiredModuleUnit;
   1634 
   1635                 if (!resources.RequiredModule.empty())
   1636                 {
   1637                     requiredModuleUnit = CreateRequiredModuleUnit(resources.RequiredModule, powerShellPackageUnit);
   1638 
   1639                     // Apply the unit to make sure it's on the system.
   1640                     context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ Utility::ConvertToUTF8(resources.RequiredModule) }) << std::endl;
   1641                     auto applyResult = ApplyUnit(context, requiredModuleUnit.value());
   1642                     if (SUCCEEDED(applyResult.ResultInformation().ResultCode()))
   1643                     {
   1644                         configContext.Set().Units().Append(requiredModuleUnit.value());
   1645                     }
   1646                     else
   1647                     {
   1648                         AICLI_LOG(Config, Warning, << "Failed to ensure module. [" << Utility::ConvertToUTF8(resources.RequiredModule) << "] Related settings will not be exported.");
   1649                         LogFailedGetConfigurationUnitDetails(requiredModuleUnit.value(), applyResult.ResultInformation());
   1650                         context.Reporter.Warn() << Resource::String::ConfigurationExportInstallRequiredModuleFailed << std::endl;
   1651                         continue;
   1652                     }
   1653                 }
   1654 
   1655                 for (const auto& resourceInfo : resources.ResourceInfos)
   1656                 {
   1657                     auto resourceUnit = CreateConfigurationUnitFromUnitType(resourceInfo.UnitType);
   1658                     auto exportedUnits = ExportUnit(context, resourceUnit);
   1659 
   1660                     if (requiredModuleUnit)
   1661                     {
   1662                         AddDependentUnit(exportedUnits, requiredModuleUnit.value());
   1663                     }
   1664 
   1665                     // The dynamic processor factory does not support operating elevated units without a set.
   1666                     // Luckily the Get/Export for all PreDefinedResources do not require elevation.
   1667                     // Here we add elevation environment to exported results.
   1668                     if (resourceInfo.ElevationRequired)
   1669                     {
   1670                         AddElevatedEnvironment(exportedUnits);
   1671                     }
   1672 
   1673                     for (auto exportedUnit : exportedUnits)
   1674                     {
   1675                         configContext.Set().Units().Append(std::move(exportedUnit));
   1676                     }
   1677                 }
   1678             }
   1679         }
   1680 
   1681         void ProcessPackagesForConfigurationExportAll(Execution::Context& context)
   1682         {
   1683             ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   1684             std::wstring sourceUnitType = GetWinGetSourceUnitType(configContext);
   1685             std::wstring packageUnitType = GetWinGetPackageUnitType(configContext);
   1686 
   1687             // This will be later used by per package settings export.
   1688             std::vector<IConfigurationUnitProcessorDetails> unitProcessors;
   1689             try
   1690             {
   1691                 unitProcessors = GetAllUnitProcessors(context);
   1692             }
   1693             catch (...)
   1694             {
   1695                 AICLI_LOG(Config, Warning, << "Finding unit processors failed. Individual package settings will not be exported.");
   1696                 context.Reporter.Warn() << Resource::String::ConfigurationExportFailedToGetUnitProcessors << std::endl;
   1697             }
   1698 
   1699             auto exclusionList = PackageSettingsExclusionList();
   1700 
   1701             // Filter out processors in exclusion list.
   1702             for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */)
   1703             {
   1704                 bool processorRemoved = false;
   1705                 for (const auto& exclusionItem : exclusionList)
   1706                 {
   1707                     if (Utility::CaseInsensitiveStartsWith(itr->UnitType(), exclusionItem))
   1708                     {
   1709                         itr = unitProcessors.erase(itr);
   1710                         processorRemoved = true;
   1711                         break;
   1712                     }
   1713                 }
   1714 
   1715                 if (!processorRemoved)
   1716                 {
   1717                     itr++;
   1718                 }
   1719             }
   1720 
   1721             for (const auto& source : context.Get<Execution::Data::PackageCollection>().Sources)
   1722             {
   1723                 // Create WinGetSource unit for non well known source.
   1724                 std::optional<ConfigurationUnit> sourceUnit;
   1725                 if (!CheckForWellKnownSource(source.Details))
   1726                 {
   1727                     sourceUnit = anon::CreateWinGetSourceUnit(source, sourceUnitType);
   1728                     configContext.Set().Units().Append(sourceUnit.value());
   1729                 }
   1730 
   1731                 for (const auto& package : source.Packages)
   1732                 {
   1733                     auto packageUnit = anon::CreateWinGetPackageUnit(package, source, context.Args.Contains(Args::Type::IncludeVersions), sourceUnit, packageUnitType);
   1734                     configContext.Set().Units().Append(packageUnit);
   1735 
   1736                     // Try package settings export.
   1737                     for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */)
   1738                     {
   1739                         IConfigurationUnitProcessorDetails3 unitProcessor3;
   1740                         itr->try_as(unitProcessor3);
   1741                         if (Filesystem::IsParentPath(std::filesystem::path{ std::wstring{ unitProcessor3.Path() } }, package.InstalledLocation))
   1742                         {
   1743                             ConfigurationUnit configUnit = anon::CreateConfigurationUnitFromUnitType(
   1744                                 unitProcessor3.UnitType(),
   1745                                 Utility::ConvertToUTF8(packageUnit.Identifier()));
   1746 
   1747                             auto exportedUnits = anon::ExportUnit(context, configUnit);
   1748                             anon::AddDependentUnit(exportedUnits, packageUnit);
   1749 
   1750                             for (auto exportedUnit : exportedUnits)
   1751                             {
   1752                                 configContext.Set().Units().Append(exportedUnit);
   1753                             }
   1754 
   1755                             // Remove the unit processor from the list after export.
   1756                             itr = unitProcessors.erase(itr);
   1757                         }
   1758                         else
   1759                         {
   1760                             itr++;
   1761                         }
   1762                     }
   1763                 }
   1764             }
   1765         }
   1766 
   1767         void ProcessPackagesForConfigurationExportSingle(Execution::Context& context)
   1768         {
   1769             ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   1770 
   1771             // When exporting single WinGetPackage unit, the WinGetPackage unit can be used as a dependent unit for following configuration unit.
   1772             std::optional<ConfigurationUnit> singlePackageUnit;
   1773 
   1774             if (context.Args.Contains(Execution::Args::Type::ConfigurationExportPackageId))
   1775             {
   1776                 const auto& exportSources = context.Get<Execution::Data::PackageCollection>().Sources;
   1777                 // There should be 1 package under 1 source.
   1778                 THROW_HR_IF(E_UNEXPECTED, exportSources.size() != 1 || exportSources[0].Packages.size() != 1);
   1779 
   1780                 std::optional<ConfigurationUnit> sourceUnit;
   1781                 if (!CheckForWellKnownSource(exportSources[0].Details))
   1782                 {
   1783                     sourceUnit = anon::CreateWinGetSourceUnit(exportSources[0], GetWinGetSourceUnitType(configContext));
   1784                     configContext.Set().Units().Append(sourceUnit.value());
   1785                 }
   1786 
   1787                 singlePackageUnit = anon::CreateWinGetPackageUnit(exportSources[0].Packages[0], exportSources[0], context.Args.Contains(Args::Type::IncludeVersions), sourceUnit, GetWinGetPackageUnitType(configContext));
   1788                 configContext.Set().Units().Append(singlePackageUnit.value());
   1789             }
   1790 
   1791             if (context.Args.Contains(Execution::Args::Type::ConfigurationExportModule, Execution::Args::Type::ConfigurationExportResource))
   1792             {
   1793                 auto configUnit = anon::CreateConfigurationUnitFromModuleResource(
   1794                     context.Args.GetArg(Args::Type::ConfigurationExportModule),
   1795                     context.Args.GetArg(Args::Type::ConfigurationExportResource),
   1796                     singlePackageUnit ? Utility::ConvertToUTF8(singlePackageUnit->Identifier()) : "",
   1797                     Utility::Version{ Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) });
   1798 
   1799                 auto exportedUnits = anon::ExportUnit(context, configUnit, true);
   1800 
   1801                 if (singlePackageUnit)
   1802                 {
   1803                     anon::AddDependentUnit(exportedUnits, singlePackageUnit.value());
   1804                 }
   1805 
   1806                 for (auto exportedUnit : exportedUnits)
   1807                 {
   1808                     configContext.Set().Units().Append(exportedUnit);
   1809                 }
   1810             }
   1811         }
   1812 
   1813         bool HistorySetMatchesInput(const ConfigurationSet& set, const std::string& foldedInput)
   1814         {
   1815             if (foldedInput.empty())
   1816             {
   1817                 return false;
   1818             }
   1819 
   1820             if (Utility::FoldCase(Utility::NormalizedString{ set.Name() }) == foldedInput)
   1821             {
   1822                 return true;
   1823             }
   1824 
   1825             std::ostringstream identifierStream;
   1826             identifierStream << set.InstanceIdentifier();
   1827             std::string identifier = identifierStream.str();
   1828             THROW_HR_IF(E_UNEXPECTED, identifier.empty());
   1829 
   1830             std::size_t startPosition = 0;
   1831             if (identifier[0] == '{' && foldedInput[0] != '{')
   1832             {
   1833                 startPosition = 1;
   1834             }
   1835 
   1836             std::string_view identifierView = identifier;
   1837             identifierView = identifierView.substr(startPosition);
   1838 
   1839             return Utility::CaseInsensitiveStartsWith(identifierView, foldedInput);
   1840         }
   1841 
   1842         Resource::LocString ToLocString(ConfigurationSetState state)
   1843         {
   1844             switch (state)
   1845             {
   1846             case ConfigurationSetState::Pending:
   1847                 return Resource::String::ConfigurationSetStatePending;
   1848             case ConfigurationSetState::InProgress:
   1849                 return Resource::String::ConfigurationSetStateInProgress;
   1850             case ConfigurationSetState::Completed:
   1851                 return Resource::String::ConfigurationSetStateCompleted;
   1852             case ConfigurationSetState::Unknown:
   1853             default:
   1854                 return Resource::String::ConfigurationSetStateUnknown;
   1855             }
   1856         }
   1857 
   1858         Resource::LocString ToLocString(ConfigurationUnitState state)
   1859         {
   1860             switch (state)
   1861             {
   1862             case ConfigurationUnitState::Pending:
   1863                 return Resource::String::ConfigurationUnitStatePending;
   1864             case ConfigurationUnitState::InProgress:
   1865                 return Resource::String::ConfigurationUnitStateInProgress;
   1866             case ConfigurationUnitState::Completed:
   1867                 return Resource::String::ConfigurationUnitStateCompleted;
   1868             case ConfigurationUnitState::Skipped:
   1869                 return Resource::String::ConfigurationUnitStateSkipped;
   1870             case ConfigurationUnitState::Unknown:
   1871             default:
   1872                 return Resource::String::ConfigurationUnitStateUnknown;
   1873             }
   1874         }
   1875 
   1876         std::string_view ToString(ConfigurationChangeEventType type)
   1877         {
   1878             switch (type)
   1879             {
   1880             case ConfigurationChangeEventType::SetAdded:
   1881                 return "SetAdded";
   1882             case ConfigurationChangeEventType::SetStateChanged:
   1883                 return "SetStateChanged";
   1884             case ConfigurationChangeEventType::SetRemoved:
   1885                 return "SetRemoved";
   1886             case ConfigurationChangeEventType::Unknown:
   1887             default:
   1888                 return "Unknown";
   1889             }
   1890         }
   1891 
   1892         std::string_view ToString(ConfigurationUnitResultSource source)
   1893         {
   1894             switch (source)
   1895             {
   1896             case ConfigurationUnitResultSource::Internal:
   1897                 return "Internal";
   1898             case ConfigurationUnitResultSource::ConfigurationSet:
   1899                 return "ConfigurationSet";
   1900             case ConfigurationUnitResultSource::UnitProcessing:
   1901                 return "UnitProcessing";
   1902             case ConfigurationUnitResultSource::SystemState:
   1903                 return "SystemState";
   1904             case ConfigurationUnitResultSource::Precondition:
   1905                 return "Precondition";
   1906             case ConfigurationUnitResultSource::None:
   1907             default:
   1908                 return "None";
   1909             }
   1910         }
   1911     }
   1912 
   1913     void CreateConfigurationProcessor(Context& context)
   1914     {
   1915         anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ anon::CreateConfigurationSetProcessorFactory(context) });
   1916     }
   1917 
   1918     void CreateConfigurationProcessorWithoutFactory(Execution::Context& context)
   1919     {
   1920         anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ IConfigurationSetProcessorFactory{ nullptr } });
   1921     }
   1922 
   1923     void OpenConfigurationSet(Context& context)
   1924     {
   1925         if (context.Args.Contains(Args::Type::ConfigurationFile))
   1926         {
   1927             std::string argPath{ context.Args.GetArg(Args::Type::ConfigurationFile) };
   1928             anon::OpenConfigurationSet(context, argPath, true);
   1929         }
   1930         else
   1931         {
   1932             THROW_HR_IF(E_UNEXPECTED, !context.Args.Contains(Args::Type::ConfigurationHistoryItem));
   1933 
   1934             context <<
   1935                 GetConfigurationSetHistory <<
   1936                 SelectSetFromHistory;
   1937         }
   1938     }
   1939 
   1940     void CreateOrOpenConfigurationSet::operator()(Context& context) const
   1941     {
   1942         std::string argPath{ context.Args.GetArg(Args::Type::OutputFile) };
   1943 
   1944         if (std::filesystem::exists(argPath) && !m_createAlways)
   1945         {
   1946             anon::OpenConfigurationSet(context, argPath, false);
   1947         }
   1948         else
   1949         {
   1950             ConfigurationSet set;
   1951             set.SchemaVersion(Utility::ConvertToUTF16(m_defaultSchemaVersion));
   1952             set.Environment().ProcessorIdentifier(ConfigurationRemoting::ToString(ConfigurationRemoting::ProcessorEngine::DSCv3));
   1953 
   1954             std::wstring argPathWide = Utility::ConvertToUTF16(argPath);
   1955             auto absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPathWide });
   1956             anon::SetNameAndOrigin(set, absolutePath);
   1957 
   1958             context.Get<Data::ConfigurationContext>().Set(set);
   1959         }
   1960     }
   1961 
   1962     void ShowConfigurationSet(Context& context)
   1963     {
   1964         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   1965 
   1966         if (configContext.Set().Units().Size() == 0)
   1967         {
   1968             context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl;
   1969             // This isn't an error termination, but there is no reason to proceed.
   1970             AICLI_TERMINATE_CONTEXT(S_FALSE);
   1971         }
   1972 
   1973         auto gettingDetailString = Resource::String::ConfigurationGettingDetails();
   1974         auto progressScope = context.Reporter.BeginAsyncProgress(true);
   1975         progressScope->Callback().SetProgressMessage(gettingDetailString);
   1976 
   1977         auto getDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::ReadOnly);
   1978         auto unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getDetailsOperation);
   1979 
   1980         bool suppressDetailsOutput = context.Args.Contains(Args::Type::ConfigurationAcceptWarning) && context.Args.Contains(Args::Type::ConfigurationSuppressPrologue);
   1981         anon::OutputHelper outputHelper{ context };
   1982         uint32_t unitsShown = 0;
   1983 
   1984         if (!suppressDetailsOutput)
   1985         {
   1986             getDetailsOperation.Progress([&](const IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult>& operation, const GetConfigurationUnitDetailsResult&)
   1987                 {
   1988                     auto threadContext = context.SetForCurrentThread();
   1989 
   1990                     unification.Reset();
   1991 
   1992                     auto unitResults = operation.GetResults().UnitResults();
   1993                     for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown)
   1994                     {
   1995                         GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown);
   1996                         anon::LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation());
   1997                         outputHelper.OutputConfigurationUnitInformation(unitResult.Unit());
   1998                     }
   1999 
   2000                     progressScope = context.Reporter.BeginAsyncProgress(true);
   2001                     progressScope->Callback().SetProgressMessage(gettingDetailString);
   2002                     unification.Progress(std::move(progressScope));
   2003                 });
   2004         }
   2005 
   2006         HRESULT hr = S_OK;
   2007         GetConfigurationSetDetailsResult result = nullptr;
   2008 
   2009         try
   2010         {
   2011             result = getDetailsOperation.get();
   2012         }
   2013         catch (...)
   2014         {
   2015             hr = LOG_CAUGHT_EXCEPTION();
   2016         }
   2017 
   2018         unification.Reset();
   2019 
   2020         if (context.IsTerminated())
   2021         {
   2022             // The context should only be terminated on us due to cancellation
   2023             context.Reporter.Error() << Resource::String::Cancelled << std::endl;
   2024             return;
   2025         }
   2026 
   2027         if (FAILED(hr))
   2028         {
   2029             // Failing to get details might not be fatal, warn about it but proceed
   2030             context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl;
   2031         }
   2032 
   2033         // Handle any missing progress callbacks that are in the results
   2034         if (result && !suppressDetailsOutput)
   2035         {
   2036             auto unitResults = result.UnitResults();
   2037             if (unitResults)
   2038             {
   2039                 for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown)
   2040                 {
   2041                     GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown);
   2042                     anon::LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation());
   2043                     outputHelper.OutputConfigurationUnitInformation(unitResult.Unit());
   2044                 }
   2045             }
   2046         }
   2047 
   2048         // Handle any units that are NOT in the results (due to an exception part of the way through)
   2049         if (!suppressDetailsOutput)
   2050         {
   2051             auto allUnits = configContext.Set().Units();
   2052             for (unitsShown; unitsShown < allUnits.Size(); ++unitsShown)
   2053             {
   2054                 ConfigurationUnit unit = allUnits.GetAt(unitsShown);
   2055                 outputHelper.OutputConfigurationUnitInformation(unit);
   2056             }
   2057         }
   2058 
   2059         if (outputHelper.ValuesTruncated)
   2060         {
   2061             // Using error to make this stand out from other warnings
   2062             context.Reporter.Error() << Resource::String::ConfigurationWarningSetViewTruncated << std::endl;
   2063         }
   2064     }
   2065 
   2066     void ShowConfigurationSetConflicts(Execution::Context& context)
   2067     {
   2068         UNREFERENCED_PARAMETER(context);
   2069     }
   2070 
   2071     void ConfirmConfigurationProcessing::operator()(Execution::Context& context) const
   2072     {
   2073         context.Reporter.Warn() << Resource::String::ConfigurationWarning << std::endl;
   2074 
   2075         if (!context.Args.Contains(Args::Type::ConfigurationAcceptWarning))
   2076         {
   2077             context << RequireInteractivity(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED);
   2078             if (context.IsTerminated())
   2079             {
   2080                 return;
   2081             }
   2082 
   2083             auto promptString = m_isApply ? Resource::String::ConfigurationWarningPromptApply : Resource::String::ConfigurationWarningPromptTest;
   2084             if (!context.Reporter.PromptForBoolResponse(promptString, Reporter::Level::Warning, false))
   2085             {
   2086                 AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED);
   2087             }
   2088         }
   2089     }
   2090 
   2091     void ApplyConfigurationSet(Execution::Context& context)
   2092     {
   2093         ApplyConfigurationSetResult result = nullptr;
   2094         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2095 
   2096         {
   2097             auto applyOperation = configContext.Processor().ApplySetAsync(configContext.Set(), ApplyConfigurationSetFlags::None);
   2098             anon::ApplyConfigurationSetProgressOutput progress{ context, applyOperation };
   2099 
   2100             result = applyOperation.get();
   2101             progress.HandleUnreportedProgress(result);
   2102         }
   2103 
   2104         if (FAILED(result.ResultCode()))
   2105         {
   2106             context.Reporter.Error() << Resource::String::ConfigurationFailedToApply << std::endl;
   2107 
   2108             // TODO: Summarize failed configuration units, especially if we put more output for each one during execution
   2109 
   2110             AICLI_TERMINATE_CONTEXT(result.ResultCode());
   2111         }
   2112         else
   2113         {
   2114             context.Reporter.Info() << Resource::String::ConfigurationSuccessfullyApplied << std::endl;
   2115         }
   2116     }
   2117 
   2118     void TestConfigurationSet(Execution::Context& context)
   2119     {
   2120         TestConfigurationSetResult result = nullptr;
   2121         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2122 
   2123         {
   2124             auto testOperation = configContext.Processor().TestSetAsync(configContext.Set());
   2125             anon::TestConfigurationSetProgressOutput progress{ context, testOperation };
   2126 
   2127             result = testOperation.get();
   2128             progress.HandleUnreportedProgress(result);
   2129         }
   2130 
   2131         switch (result.TestResult())
   2132         {
   2133         case ConfigurationTestResult::Failed:
   2134             context.Reporter.Error() << Resource::String::ConfigurationFailedToTest << std::endl;
   2135             AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_FAILED);
   2136             break;
   2137         case ConfigurationTestResult::Negative:
   2138             context.Reporter.Warn() << Resource::String::ConfigurationNotInDesiredState << std::endl;
   2139             context.SetTerminationHR(S_FALSE);
   2140             break;
   2141         case ConfigurationTestResult::NotRun:
   2142             context.Reporter.Warn() << Resource::String::ConfigurationNoTestRun << std::endl;
   2143             AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_NOT_RUN);
   2144             break;
   2145         case ConfigurationTestResult::Positive:
   2146             context.Reporter.Info() << Resource::String::ConfigurationInDesiredState << std::endl;
   2147             break;
   2148         default: // ConfigurationTestResult::Unknown
   2149             context.Reporter.Error() << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(result.TestResult())) << std::endl;
   2150             AICLI_TERMINATE_CONTEXT(E_FAIL);
   2151             break;
   2152         }
   2153     }
   2154 
   2155     void ValidateConfigurationSetSemantics(Execution::Context& context)
   2156     {
   2157         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2158 
   2159         if (configContext.Set().Units().Size() == 0)
   2160         {
   2161             context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl;
   2162             // This isn't an error termination, but there is no reason to proceed.
   2163             AICLI_TERMINATE_CONTEXT(S_FALSE);
   2164         }
   2165 
   2166         ApplyConfigurationSetResult result = configContext.Processor().ApplySet(configContext.Set(), ApplyConfigurationSetFlags::PerformConsistencyCheckOnly);
   2167 
   2168         if (FAILED(result.ResultCode()))
   2169         {
   2170             for (const auto& unitResult : result.UnitResults())
   2171             {
   2172                 IConfigurationUnitResultInformation resultInformation = unitResult.ResultInformation();
   2173                 winrt::hresult resultCode = resultInformation.ResultCode();
   2174 
   2175                 if (FAILED(resultCode))
   2176                 {
   2177                     ConfigurationUnit unit = unitResult.Unit();
   2178 
   2179                     anon::OutputConfigurationUnitHeader(context, unit, unit.Type());
   2180 
   2181                     switch (resultCode)
   2182                     {
   2183                     case WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER:
   2184                         context.Reporter.Error() << "  "_liv << Resource::String::ConfigurationUnitHasDuplicateIdentifier(Utility::LocIndString{ Utility::ConvertToUTF8(unit.Identifier()) }) << std::endl;
   2185                         break;
   2186                     case WINGET_CONFIG_ERROR_MISSING_DEPENDENCY:
   2187                         context.Reporter.Error() << "  "_liv << Resource::String::ConfigurationUnitHasMissingDependency(Utility::LocIndString{ Utility::ConvertToUTF8(resultInformation.Details()) }) << std::endl;
   2188                         break;
   2189                     case WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED:
   2190                         context.Reporter.Error() << "  "_liv << Resource::String::ConfigurationUnitIsPartOfDependencyCycle << std::endl;
   2191                         break;
   2192                     default:
   2193                         context.Reporter.Error() << "  "_liv << Resource::String::ConfigurationUnitFailed(static_cast<int32_t>(resultCode)) << std::endl;
   2194                         break;
   2195                     }
   2196                 }
   2197             }
   2198 
   2199             AICLI_TERMINATE_CONTEXT(result.ResultCode());
   2200         }
   2201     }
   2202 
   2203     void ValidateConfigurationSetUnitProcessors(Execution::Context& context)
   2204     {
   2205         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2206 
   2207         // TODO: We could optimize this by creating a set with unique resource units
   2208 
   2209         // First get the local details
   2210         auto gettingDetailString = Resource::String::ConfigurationGettingDetails();
   2211         auto progressScope = context.Reporter.BeginAsyncProgress(true);
   2212         progressScope->Callback().SetProgressMessage(gettingDetailString);
   2213 
   2214         auto getLocalDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Local);
   2215         auto unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getLocalDetailsOperation);
   2216 
   2217         HRESULT getLocalHR = S_OK;
   2218         GetConfigurationSetDetailsResult getLocalResult = nullptr;
   2219 
   2220         try
   2221         {
   2222             getLocalResult = getLocalDetailsOperation.get();
   2223         }
   2224         catch (...)
   2225         {
   2226             getLocalHR = LOG_CAUGHT_EXCEPTION();
   2227         }
   2228 
   2229         unification.Reset();
   2230 
   2231         if (context.IsTerminated())
   2232         {
   2233             // The context should only be terminated on us due to cancellation
   2234             context.Reporter.Error() << Resource::String::Cancelled << std::endl;
   2235             return;
   2236         }
   2237 
   2238         if (FAILED(getLocalHR))
   2239         {
   2240             // Failing to get details might not be fatal, warn about it but proceed
   2241             context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl;
   2242         }
   2243 
   2244         // Next get the details from the catalog
   2245         progressScope = context.Reporter.BeginAsyncProgress(true);
   2246         progressScope->Callback().SetProgressMessage(gettingDetailString);
   2247 
   2248         auto getCatalogDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Catalog);
   2249         unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getCatalogDetailsOperation);
   2250 
   2251         HRESULT getCatalogHR = S_OK;
   2252         GetConfigurationSetDetailsResult getCatalogResult = nullptr;
   2253 
   2254         try
   2255         {
   2256             getCatalogResult = getCatalogDetailsOperation.get();
   2257         }
   2258         catch (...)
   2259         {
   2260             getCatalogHR = LOG_CAUGHT_EXCEPTION();
   2261         }
   2262 
   2263         unification.Reset();
   2264 
   2265         if (context.IsTerminated())
   2266         {
   2267             // The context should only be terminated on us due to cancellation
   2268             context.Reporter.Error() << Resource::String::Cancelled << std::endl;
   2269             return;
   2270         }
   2271 
   2272         if (FAILED(getCatalogHR))
   2273         {
   2274             // Failing to get the catalog details means that we can't really get give much of a meaningful response.
   2275             context.Reporter.Error() << Resource::String::ConfigurationFailedToGetDetails << std::endl;
   2276             AICLI_TERMINATE_CONTEXT(getCatalogHR);
   2277         }
   2278 
   2279         auto units = configContext.Set().Units();
   2280         auto localUnitResults = getLocalResult ? getLocalResult.UnitResults() : nullptr;
   2281         if (localUnitResults && units.Size() != localUnitResults.Size())
   2282         {
   2283             AICLI_LOG(Config, Error, << "The details result size did not match the set size: Set[" << units.Size() << "], Local[" << localUnitResults.Size() << "]");
   2284             THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED);
   2285         }
   2286 
   2287         auto catalogUnitResults = getCatalogResult.UnitResults();
   2288         if (units.Size() != catalogUnitResults.Size())
   2289         {
   2290             AICLI_LOG(Config, Error, << "The details result sizes did not match the set size: Set[" << units.Size() << "], Catalog[" << catalogUnitResults.Size() << "]");
   2291             THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED);
   2292         }
   2293 
   2294         bool foundIssue = false;
   2295 
   2296         // Now that we have the entire set of local and catalog details, process each unit
   2297         for (uint32_t i = 0; i < units.Size(); ++i)
   2298         {
   2299             const ConfigurationUnit& unit = units.GetAt(i);
   2300             GetConfigurationUnitDetailsResult localUnitResult = localUnitResults ? localUnitResults.GetAt(i) : nullptr;
   2301             GetConfigurationUnitDetailsResult catalogUnitResult = catalogUnitResults.GetAt(i);
   2302             IConfigurationUnitProcessorDetails catalogDetails = catalogUnitResult.Details();
   2303 
   2304             bool needsHeader = true;
   2305             auto outputHeaderIfNeeded = [&]()
   2306             {
   2307                 if (needsHeader)
   2308                 {
   2309                     anon::OutputConfigurationUnitHeader(context, unit, unit.Type());
   2310 
   2311                     needsHeader = false;
   2312                     foundIssue = true;
   2313                 }
   2314             };
   2315 
   2316             if (anon::GetValueSetString(unit.Metadata(), anon::s_Directive_Module).empty())
   2317             {
   2318                 outputHeaderIfNeeded();
   2319                 context.Reporter.Warn() << "  "_liv << Resource::String::ConfigurationUnitModuleNotProvidedWarning << std::endl;
   2320             }
   2321 
   2322             if (catalogDetails)
   2323             {
   2324                 // Warn if unit is not public
   2325                 if (!catalogDetails.IsPublic())
   2326                 {
   2327                     outputHeaderIfNeeded();
   2328                     context.Reporter.Warn() << "  "_liv << Resource::String::ConfigurationUnitNotPublicWarning << std::endl;
   2329                 }
   2330 
   2331                 // Since it is available, no more checks are needed
   2332                 continue;
   2333             }
   2334             // Everything below here is due to not finding in the catalog search
   2335 
   2336             if (FAILED(catalogUnitResult.ResultInformation().ResultCode()))
   2337             {
   2338                 outputHeaderIfNeeded();
   2339                 anon::OutputUnitRunFailure(context, unit, catalogUnitResult.ResultInformation());
   2340                 continue;
   2341             }
   2342 
   2343             // If not already prerelease, try with prerelease and warn if found
   2344             std::optional<bool> allowPrereleaseDirective = anon::GetValueSetBool(unit.Metadata(), anon::s_Directive_AllowPrerelease);
   2345             if (!allowPrereleaseDirective || !allowPrereleaseDirective.value())
   2346             {
   2347                 // Check if the configuration unit is prerelease but the author forgot it
   2348                 ConfigurationUnit clone = unit.Copy();
   2349                 clone.Metadata().Insert(anon::s_Directive_AllowPrerelease, PropertyValue::CreateBoolean(true));
   2350 
   2351                 progressScope = context.Reporter.BeginAsyncProgress(true);
   2352                 progressScope->Callback().SetProgressMessage(gettingDetailString);
   2353 
   2354                 auto getUnitDetailsOperation = configContext.Processor().GetUnitDetailsAsync(clone, ConfigurationUnitDetailFlags::Catalog);
   2355                 auto unitUnification = anon::CreateProgressCancellationUnification(std::move(progressScope), getUnitDetailsOperation);
   2356 
   2357                 IConfigurationUnitProcessorDetails prereleaseDetails;
   2358 
   2359                 try
   2360                 {
   2361                     prereleaseDetails = getUnitDetailsOperation.get().Details();
   2362                 }
   2363                 CATCH_LOG();
   2364 
   2365                 unification.Reset();
   2366 
   2367                 if (prereleaseDetails)
   2368                 {
   2369                     outputHeaderIfNeeded();
   2370                     context.Reporter.Warn() << "  "_liv << Resource::String::ConfigurationUnitNeedsPrereleaseWarning << std::endl;
   2371                     continue;
   2372                 }
   2373             }
   2374 
   2375             // If module is local, warn that we couldn't find it in the catalog
   2376             if (localUnitResult && localUnitResult.Details())
   2377             {
   2378                 outputHeaderIfNeeded();
   2379                 context.Reporter.Warn() << "  "_liv << Resource::String::ConfigurationUnitNotInCatalogWarning << std::endl;
   2380                 continue;
   2381             }
   2382 
   2383             // Finally, error that we couldn't find it at all
   2384             outputHeaderIfNeeded();
   2385             context.Reporter.Error() << "  "_liv << Resource::String::ConfigurationUnitNotFound << std::endl;
   2386         }
   2387 
   2388         if (foundIssue)
   2389         {
   2390             // Indicate that it was not a total success
   2391             AICLI_TERMINATE_CONTEXT(S_FALSE);
   2392         }
   2393     }
   2394 
   2395     void ValidateConfigurationSetUnitContents(Execution::Context& context)
   2396     {
   2397         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2398         auto units = configContext.Set().Units();
   2399         auto validationOrder = anon::GetConfigurationSetUnitValidationOrder(units.GetView());
   2400 
   2401         Configuration::WingetDscModuleUnitValidator wingetUnitValidator;
   2402 
   2403         bool foundIssues = false;
   2404         for (const auto index : validationOrder)
   2405         {
   2406             const ConfigurationUnit& unit = units.GetAt(index);
   2407             auto moduleName = Utility::ConvertToUTF8(unit.Details().ModuleName());
   2408             if (Utility::CaseInsensitiveEquals(wingetUnitValidator.ModuleName(), moduleName))
   2409             {
   2410                 bool result = wingetUnitValidator.ValidateConfigurationSetUnit(context, unit);
   2411                 if (!result)
   2412                 {
   2413                     foundIssues = true;
   2414                 }
   2415             }
   2416         }
   2417 
   2418         if (foundIssues)
   2419         {
   2420             // Indicate that it was not a total success
   2421             AICLI_TERMINATE_CONTEXT(S_FALSE);
   2422         }
   2423     }
   2424 
   2425     void ValidateAllGoodMessage(Execution::Context& context)
   2426     {
   2427         context.Reporter.Info() << Resource::String::ConfigurationValidationFoundNoIssues << std::endl;
   2428     }
   2429 
   2430     void SearchSourceForPackageExport(Execution::Context& context)
   2431     {
   2432         if (!context.Args.Contains(Args::Type::ConfigurationExportAll) && !context.Args.Contains(Args::Type::ConfigurationExportPackageId))
   2433         {
   2434             // No package export needed.
   2435             return;
   2436         }
   2437 
   2438         context <<
   2439             OpenSource() <<
   2440             OpenCompositeSource(Repository::PredefinedSource::Installed);
   2441 
   2442         if (context.Args.Contains(Args::Type::ConfigurationExportAll))
   2443         {
   2444             context <<
   2445                 SearchSourceForMany <<
   2446                 HandleSearchResultFailures <<
   2447                 EnsureMatchesFromSearchResult(OperationType::Export) <<
   2448                 SelectVersionsToExport;
   2449         }
   2450         else if (context.Args.Contains(Args::Type::ConfigurationExportPackageId))
   2451         {
   2452             context.Args.AddArg(Args::Type::Id, context.Args.GetArg(Args::Type::ConfigurationExportPackageId));
   2453             context <<
   2454                 SearchSourceForSingle <<
   2455                 Workflow::HandleSearchResultFailures <<
   2456                 Workflow::EnsureOneMatchFromSearchResult(OperationType::Export) <<
   2457                 SelectVersionsToExport;
   2458         }
   2459     }
   2460 
   2461     void PopulateConfigurationSetForExport(Execution::Context& context)
   2462     {
   2463         bool isExportAll = context.Args.Contains(Execution::Args::Type::ConfigurationExportAll);
   2464 
   2465         if (isExportAll)
   2466         {
   2467             context <<
   2468                 anon::ExportPredefinedResources <<
   2469                 SearchSourceForPackageExport <<
   2470                 anon::ProcessPackagesForConfigurationExportAll;
   2471         }
   2472         else
   2473         {
   2474             context <<
   2475                 SearchSourceForPackageExport <<
   2476                 anon::ProcessPackagesForConfigurationExportSingle;
   2477         }
   2478     }
   2479 
   2480     void WriteConfigFile(Execution::Context& context)
   2481     {
   2482         try
   2483         {
   2484             std::string argPath{ context.Args.GetArg(Args::Type::OutputFile) };
   2485 
   2486             context.Reporter.Info() << Resource::String::ConfigurationExportAddingToFile(Utility::LocIndView{ argPath }) << std::endl;
   2487 
   2488             auto tempFilePath = Runtime::GetNewTempFilePath();
   2489 
   2490             {
   2491                 std::ofstream tempStream{ tempFilePath };
   2492                 tempStream << "# Created using winget configure export " << Runtime::GetClientVersion().get() << std::endl;
   2493             }
   2494 
   2495             auto openAction = Streams::FileRandomAccessStream::OpenAsync(
   2496                 tempFilePath.wstring(),
   2497                 FileAccessMode::ReadWrite);
   2498 
   2499             auto stream = openAction.get();
   2500             stream.Seek(stream.Size());
   2501 
   2502             ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2503             configContext.Set().Serialize(openAction.get());
   2504 
   2505             auto absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPath });
   2506             std::filesystem::rename(tempFilePath, absolutePath);
   2507 
   2508             context.Reporter.Info() << Resource::String::ConfigurationExportSuccessful << std::endl;
   2509         }
   2510         catch (...)
   2511         {
   2512             context.Reporter.Error() << Resource::String::ConfigurationExportFailed << std::endl;
   2513             throw;
   2514         }
   2515     }
   2516 
   2517     void GetConfigurationSetHistory(Execution::Context& context)
   2518     {
   2519         auto progressScope = context.Reporter.BeginAsyncProgress(true);
   2520 
   2521         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2522         configContext.History(configContext.Processor().GetConfigurationHistory());
   2523     }
   2524 
   2525     void ShowConfigurationSetHistory(Execution::Context& context)
   2526     {
   2527         const auto& history = context.Get<Data::ConfigurationContext>().History();
   2528 
   2529         if (history.empty())
   2530         {
   2531             context.Reporter.Info() << Resource::String::ConfigurationHistoryEmpty << std::endl;
   2532         }
   2533         else
   2534         {
   2535             TableOutput<4> historyTable{ context.Reporter, { Resource::String::ConfigureListIdentifier, Resource::String::ConfigureListName, Resource::String::ConfigureListState, Resource::String::ConfigureListOrigin } };
   2536 
   2537             for (const auto& set : history)
   2538             {
   2539                 winrt::hstring origin = set.Path();
   2540                 if (origin.empty())
   2541                 {
   2542                     origin = set.Origin();
   2543                 }
   2544 
   2545                 historyTable.OutputLine({ Utility::ConvertGuidToString(set.InstanceIdentifier()), Utility::ConvertToUTF8(set.Name()), anon::ToLocString(set.State()), Utility::ConvertToUTF8(origin)});
   2546             }
   2547 
   2548             historyTable.Complete();
   2549         }
   2550     }
   2551 
   2552     void SelectSetFromHistory(Execution::Context& context)
   2553     {
   2554         ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>();
   2555         ConfigurationSet selectedSet{ nullptr };
   2556 
   2557         std::string foldedInput = Utility::FoldCase(context.Args.GetArg(Execution::Args::Type::ConfigurationHistoryItem));
   2558 
   2559         for (const ConfigurationSet& historySet : configContext.History())
   2560         {
   2561             if (anon::HistorySetMatchesInput(historySet, foldedInput))
   2562             {
   2563                 if (selectedSet)
   2564                 {
   2565                     selectedSet = nullptr;
   2566                     break;
   2567                 }
   2568                 else
   2569                 {
   2570                     selectedSet = historySet;
   2571                 }
   2572             }
   2573         }
   2574 
   2575         if (!selectedSet)
   2576         {
   2577             context.Reporter.Warn() << Resource::String::ConfigurationHistoryItemNotFound << std::endl;
   2578             context << ShowConfigurationSetHistory;
   2579             AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND);
   2580         }
   2581 
   2582         configContext.Set(std::move(selectedSet));
   2583     }
   2584 
   2585     void RemoveConfigurationSetHistory(Execution::Context& context)
   2586     {
   2587         auto progressScope = context.Reporter.BeginAsyncProgress(true);
   2588         context.Get<Data::ConfigurationContext>().Set().Remove();
   2589     }
   2590 
   2591     void SerializeConfigurationSetHistory(Execution::Context& context)
   2592     {
   2593         auto progressScope = context.Reporter.BeginAsyncProgress(true);
   2594         std::filesystem::path absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) });
   2595         auto openAction = Streams::FileRandomAccessStream::OpenAsync(absolutePath.wstring(), FileAccessMode::ReadWrite, StorageOpenOptions::None, Streams::FileOpenDisposition::CreateAlways);
   2596         auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); });
   2597         auto outputStream = openAction.get();
   2598 
   2599         context.Get<Data::ConfigurationContext>().Set().Serialize(outputStream);
   2600     }
   2601 
   2602     void ShowSingleConfigurationSetHistory(Execution::Context& context)
   2603     {
   2604         const auto& set = context.Get<Data::ConfigurationContext>().Set();
   2605 
   2606         // Output a table with name/value pairs for some of the set's properties. Example:
   2607         // 
   2608         // Field         Value
   2609         // ----------------------------------------------------
   2610         // Identifier    {7D5CF50E-F3C6-4333-BFE6-5A806F9EBA4E}
   2611         // Name          Test Name
   2612         // Origin        Test Origin
   2613         // Path          Test Path
   2614         // State         Completed
   2615         // First Applied 2024-07-16 21:15:13.000
   2616         // Apply Begun   2024-07-16 21:15:13.000
   2617         // Apply Ended   2024-07-16 21:15:13.000
   2618         Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue });
   2619 
   2620         table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListIdentifier }, Utility::ConvertGuidToString(set.InstanceIdentifier()) });
   2621         table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListName }, Utility::ConvertToUTF8(set.Name()) });
   2622         table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListOrigin }, Utility::ConvertToUTF8(set.Origin()) });
   2623         table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListPath }, Utility::ConvertToUTF8(set.Path()) });
   2624         table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListState }, anon::ToLocString(set.State()) });
   2625         table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListFirstApplied }, Utility::TimePointToString(winrt::clock::to_sys(set.FirstApply())) });
   2626 
   2627         auto applyBegun = set.ApplyBegun();
   2628         if (applyBegun != winrt::clock::time_point{})
   2629         {
   2630             table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyBegun }, Utility::TimePointToString(winrt::clock::to_sys(applyBegun)) });
   2631         }
   2632 
   2633         auto applyEnded = set.ApplyEnded();
   2634         if (applyEnded != winrt::clock::time_point{})
   2635         {
   2636             table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyEnded }, Utility::TimePointToString(winrt::clock::to_sys(applyEnded)) });
   2637         }
   2638 
   2639         table.Complete();
   2640 
   2641         context.Reporter.Info() << std::endl;
   2642 
   2643         // Output a table with unit state information. Groups are represented by indentation beneath their parent unit. Example:
   2644         //
   2645         // Unit                        State     Result     Details
   2646         // ------------------------------------------------------------
   2647         // Module/Resource [Name]      Completed 0x00000000
   2648         // Module2/Resource [Group]    Completed 0x00000000
   2649         // |-Module3/Resource [Child1] Completed 0x00000000
   2650         // |---Module4/Resource2       Completed 0x80004005 I failed :(
   2651         // |-Module3/Resource [Child2] Completed 0x00000000
   2652         Execution::TableOutput<4> unitTable(context.Reporter, { Resource::String::ConfigureListUnit, Resource::String::ConfigureListState, Resource::String::ConfigureListResult, Resource::String::ConfigureListResultDescription });
   2653 
   2654         struct UnitSiblings
   2655         {
   2656             size_t Depth = 0;
   2657             size_t Current = 0;
   2658             std::vector<ConfigurationUnit> Siblings;
   2659         };
   2660 
   2661         std::vector<UnitSiblings> stack;
   2662 
   2663         {
   2664             UnitSiblings initial;
   2665             auto units = set.Units();
   2666             initial.Siblings.resize(units.Size());
   2667             units.GetMany(0, initial.Siblings);
   2668             stack.emplace_back(std::move(initial));
   2669         }
   2670 
   2671         // Each item on the stack is a list of sibling units.
   2672         // Each iteration, we process the Current sibling from the group on top of the stack.
   2673         // If it is a group, we add its children as a new stack item to be processed next.
   2674         while (!stack.empty())
   2675         {
   2676             UnitSiblings& currentSiblings = stack.back();
   2677 
   2678             if (currentSiblings.Current >= currentSiblings.Siblings.size())
   2679             {
   2680                 stack.pop_back();
   2681                 continue;
   2682             }
   2683 
   2684             ConfigurationUnit& currentUnit = currentSiblings.Siblings[currentSiblings.Current++];
   2685 
   2686             std::ostringstream unitStream;
   2687 
   2688             if (currentSiblings.Depth)
   2689             {
   2690                 unitStream << '|' << std::string((currentSiblings.Depth * 2) - 1, '-');
   2691             }
   2692 
   2693             unitStream << Utility::ConvertToUTF8(currentUnit.Type());
   2694 
   2695             auto identifier = currentUnit.Identifier();
   2696             if (!identifier.empty())
   2697             {
   2698                 unitStream << " [" << Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(identifier)) << ']';
   2699             }
   2700 
   2701             auto resultInformation = currentUnit.ResultInformation();
   2702             std::ostringstream resultStream;
   2703             std::string resultDetails;
   2704 
   2705             if (resultInformation)
   2706             {
   2707                 resultStream << "0x" << Logging::SetHRFormat << resultInformation.ResultCode();
   2708 
   2709                 auto description = resultInformation.Description();
   2710                 if (description.empty())
   2711                 {
   2712                     description = resultInformation.Details();
   2713                 }
   2714 
   2715                 resultDetails = Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(description));
   2716             }
   2717 
   2718             unitTable.OutputLine({ std::move(unitStream).str(), anon::ToLocString(currentUnit.State()), std::move(resultStream).str(), std::move(resultDetails) });
   2719 
   2720             if (currentUnit.IsGroup())
   2721             {
   2722                 UnitSiblings unitChildren;
   2723                 unitChildren.Depth = currentSiblings.Depth + 1;
   2724                 auto units = currentUnit.Units();
   2725                 unitChildren.Siblings.resize(units.Size());
   2726                 units.GetMany(0, unitChildren.Siblings);
   2727                 stack.emplace_back(std::move(unitChildren));
   2728             }
   2729         }
   2730 
   2731         unitTable.Complete();
   2732     }
   2733 
   2734     void CompleteConfigurationHistoryItem(Execution::Context& context)
   2735     {
   2736         const std::string& word = context.Get<Data::CompletionData>().Word();
   2737         auto stream = context.Reporter.Completion();
   2738 
   2739         for (const auto& historyItem : ConfigurationProcessor{ IConfigurationSetProcessorFactory{ nullptr } }.GetConfigurationHistory())
   2740         {
   2741             std::ostringstream identifierStream;
   2742             identifierStream << historyItem.InstanceIdentifier();
   2743             std::string identifier = identifierStream.str();
   2744 
   2745             if (word.empty() || Utility::CaseInsensitiveContainsSubstring(identifier, word))
   2746             {
   2747                 stream << '"' << identifier << '"' << std::endl;
   2748             }
   2749 
   2750             std::string name = Utility::ConvertToUTF8(historyItem.Name());
   2751 
   2752             if (word.empty() || Utility::CaseInsensitiveStartsWith(name, word))
   2753             {
   2754                 stream << '"' << name << '"' << std::endl;
   2755             }
   2756         }
   2757     }
   2758 
   2759     void MonitorConfigurationStatus(Execution::Context& context)
   2760     {
   2761         auto& configurationContext = context.Get<Data::ConfigurationContext>();
   2762 
   2763         std::mutex activeSetMutex;
   2764         ConfigurationSet activeSet{ nullptr };
   2765         decltype(activeSet.ConfigurationSetChange(winrt::auto_revoke, nullptr)) activeSetRevoker;
   2766 
   2767         auto setChangeHandler = [&](const ConfigurationSet& set, const ConfigurationSetChangeData& changeData)
   2768             {
   2769                 if (changeData.Change() == ConfigurationSetChangeEventType::SetStateChanged)
   2770                 {
   2771                     context.Reporter.Info() << "(SetStateChanged) " << set.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.SetState()) << std::endl;
   2772                 }
   2773                 else if (changeData.Change() == ConfigurationSetChangeEventType::UnitStateChanged)
   2774                 {
   2775                     context.Reporter.Info() << "(UnitStateChanged) " << changeData.Unit().InstanceIdentifier() << " :: " << anon::ToLocString(changeData.UnitState()) << std::endl;
   2776 
   2777                     auto resultInformation = changeData.ResultInformation();
   2778                     if (resultInformation)
   2779                     {
   2780                         context.Reporter.Info() << "    [" << anon::ToString(resultInformation.ResultSource()) << "] :: 0x" << Logging::SetHRFormat << resultInformation.ResultCode() << std::endl;
   2781                     }
   2782                 }
   2783             };
   2784 
   2785         auto setActiveSet = [&](const ConfigurationSet& set, bool force)
   2786             {
   2787                 std::lock_guard<std::mutex> lock{ activeSetMutex };
   2788 
   2789                 if (force || !activeSet)
   2790                 {
   2791                     activeSet = set;
   2792                     activeSetRevoker = activeSet.ConfigurationSetChange(winrt::auto_revoke, setChangeHandler);
   2793                 }
   2794             };
   2795 
   2796         auto processorRevoker = configurationContext.Processor().ConfigurationChange(winrt::auto_revoke, [&](const ConfigurationSet& set, const ConfigurationChangeData& changeData)
   2797             {
   2798                 context.Reporter.Info() << '[' << anon::ToString(changeData.Change()) << "] " << changeData.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.State()) << std::endl;
   2799 
   2800                 if (changeData.Change() == ConfigurationChangeEventType::SetStateChanged && changeData.State() == ConfigurationSetState::InProgress)
   2801                 {
   2802                     setActiveSet(set, true);
   2803                 }
   2804             });
   2805 
   2806         for (ConfigurationSet& historySet : configurationContext.History())
   2807         {
   2808             if (historySet.State() == ConfigurationSetState::InProgress)
   2809             {
   2810                 setActiveSet(historySet, false);
   2811             }
   2812         }
   2813 
   2814         for (;;)
   2815         {
   2816             std::this_thread::sleep_for(250ms);
   2817             if (context.IsTerminated())
   2818             {
   2819                 return;
   2820             }
   2821         }
   2822     }
   2823 }