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 }