winget-cli

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

commit 2e91480e7bb2a77ce13cde31d51714f42381b739
parent 414cd9aca99e3e6ec1925372b233605717fe67cf
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Mon, 29 Jul 2024 10:46:35 -0700

Store configuration status and enable remote watch events (#4647)

## Change
This change stores ongoing status information during configuration
application in the database. This data is then retrievable via the
existing API surface from a set retrieved from
`GetConfigurationHistory`. The data includes: configuration set state,
configuration set apply start/stop times, configuration unit state, and
configuration unit results.

In addition, the existing events on the `ConfigurationProcessor` and
`ConfigurationSet` are implemented to send data when appropriate. This
works across processes, using named events to indicate changes and the
database to carry the actual data.

`winget configure list` is updated to expose this information, for
example:

```
PS > wingetdev configure list
Identifier                             Name      State     Origin
--------------------------------------------------------------------
{7D5CF50E-F3C6-4333-BFE6-5A806F9EBA4E} Test Name Completed Test Path
{51837FDE-C290-408B-A847-B176DB184B89} Test Name Completed Test Path
{E7367B3D-07A2-4875-A190-C369B79E6F5B} Test Name Completed Test Path
{B186F2D0-3245-4AD1-A712-260906033E57} Test Name Completed Test Path
{2AA76541-A0DF-45EE-91F4-401F93FA88CD} Test Name Completed Test Path
PS > wingetdev configure list --history 7D5CF50E
Field         Value
----------------------------------------------------
Identifier    {7D5CF50E-F3C6-4333-BFE6-5A806F9EBA4E}
Name          Test Name
Origin        Test Origin
Path          Test Path
State         Completed
First Applied 2024-07-16 21:15:13.000
Apply Begun   2024-07-16 21:15:13.000
Apply Ended   2024-07-16 21:15:13.000

Unit                         State     Result
-------------------------------------------------
Module/Resource [Name]       Completed 0x00000000
Module/Resource2 [Name2]     Completed 0x00000000
Module2/Resource [Group]     Completed 0x00000000
|-Module3/Resource [Child1]  Completed 0x00000000
|-Module4/Resource2 [Child2] Completed 0x00000000
```
Diffstat:
M.github/actions/spelling/allow.txt | 3++-
Msrc/AppInstallerCLICore/Argument.cpp | 2++
Msrc/AppInstallerCLICore/Commands/ConfigureListCommand.cpp | 5+++++
Msrc/AppInstallerCLICore/ExecutionArgs.h | 1+
Msrc/AppInstallerCLICore/Resources.h | 16++++++++++++++++
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp | 264++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.h | 6++++++
Msrc/AppInstallerCLIE2ETests/ConfigureShowCommand.cs | 2+-
Msrc/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs | 7+++++--
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/SQLiteDynamicStorage.cpp | 6+++---
Msrc/AppInstallerCLITests/SQLiteWrapper.cpp | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.cpp | 2+-
Msrc/AppInstallerSharedLib/DateTime.cpp | 7+++++++
Msrc/AppInstallerSharedLib/Public/AppInstallerDateTime.h | 3+++
Msrc/AppInstallerSharedLib/Public/winget/SQLiteDynamicStorage.h | 6+++---
Msrc/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h | 14++++++++++++--
Msrc/AppInstallerSharedLib/Public/winget/SQLiteWrapper.h | 33+++++++++++++++++++++++++++++++++
Msrc/AppInstallerSharedLib/SQLiteDynamicStorage.cpp | 8++++----
Msrc/AppInstallerSharedLib/SQLiteStatementBuilder.cpp | 26+++++++++++++++-----------
Msrc/AppInstallerSharedLib/SQLiteWrapper.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs | 4++++
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcDiscoverer.cs | 2+-
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs | 16++++++++++++++--
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationHistoryTests.cs | 87++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorApplyTests.cs | 8++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationChangeData.h | 2+-
Msrc/Microsoft.Management.Configuration/ConfigurationProcessor.cpp | 103+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/Microsoft.Management.Configuration/ConfigurationProcessor.h | 41+++++++++++++++++++++--------------------
Msrc/Microsoft.Management.Configuration/ConfigurationSequencer.cpp | 9+++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationSequencer.h | 1+
Msrc/Microsoft.Management.Configuration/ConfigurationSet.cpp | 60++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/Microsoft.Management.Configuration/ConfigurationSet.h | 12++++++------
Msrc/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp | 9+++++++--
Msrc/Microsoft.Management.Configuration/ConfigurationSetChangeData.h | 5++++-
Asrc/Microsoft.Management.Configuration/ConfigurationStatus.cpp | 410+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/ConfigurationStatus.h | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationUnit.cpp | 7+++++--
Msrc/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp | 8++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.h | 1+
Msrc/Microsoft.Management.Configuration/Database/ConfigurationDatabase.cpp | 459+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/Microsoft.Management.Configuration/Database/ConfigurationDatabase.h | 87++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/Microsoft.Management.Configuration/Database/Schema/0_1/Interface.h | 3+++
Msrc/Microsoft.Management.Configuration/Database/Schema/0_1/Interface_0_1.cpp | 6++++++
Msrc/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.h | 9+++++++++
Msrc/Microsoft.Management.Configuration/Database/Schema/0_2/Interface.h | 2+-
Msrc/Microsoft.Management.Configuration/Database/Schema/0_2/Interface_0_2.cpp | 9++++++---
Msrc/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.h | 13+++++++++++--
Asrc/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.cpp | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.h | 29+++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/Database/Schema/0_3/Interface.h | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/Database/Schema/0_3/Interface_0_3.cpp | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.cpp | 365+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.cpp | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.h | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl | 6+++---
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj | 8++++++++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters | 27+++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp | 13++++++-------
Msrc/Microsoft.Management.Configuration/Telemetry/Telemetry.h | 1-
64 files changed, 2953 insertions(+), 334 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -194,6 +194,7 @@ norestart normalizednameandpublisher normalizedpackagenameandpublisher notcontains +NTAPI NTSTATUS nullsoft nunit @@ -330,7 +331,7 @@ Testrun testsettingname TEXTFORMAT TEXTINCLUDE -Threadpool +threadpool tpl TRACELOGGING triaged diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -219,6 +219,8 @@ namespace AppInstaller::CLI return { type, "history"_liv, 'h', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; case Execution::Args::Type::ConfigurationHistoryRemove: return { type, "remove"_liv }; + case Execution::Args::Type::ConfigurationStatusWatch: + return { type, "live"_liv }; // Download command case Execution::Args::Type::DownloadDirectory: diff --git a/src/AppInstallerCLICore/Commands/ConfigureListCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureListCommand.cpp @@ -15,6 +15,7 @@ namespace AppInstaller::CLI Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard }, Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, Argument{ Execution::Args::Type::ConfigurationHistoryRemove, Resource::String::ConfigurationHistoryRemoveArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ConfigurationStatusWatch, Resource::String::ConfigurationStatusWatchArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }, }; } @@ -58,6 +59,10 @@ namespace AppInstaller::CLI context << ShowSingleConfigurationSetHistory; } } + else if (context.Args.Contains(Execution::Args::Type::ConfigurationStatusWatch)) + { + context << MonitorConfigurationStatus; + } else { context << ShowConfigurationSetHistory; diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -132,6 +132,7 @@ namespace AppInstaller::CLI::Execution ConfigurationExportResource, ConfigurationHistoryItem, ConfigurationHistoryRemove, + ConfigurationStatusWatch, // Common arguments NoVT, // Disable VirtualTerminal outputs diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -99,7 +99,12 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNoTestRun); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotInDesiredState); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateCompleted); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStatePending); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateUnknown); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationStatusWatchArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnexpectedTestResult); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitAssertHadNegativeResult); @@ -131,6 +136,11 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitReturnedInvalidResult); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSettingConfigRoot); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateCompleted); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStatePending); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateUnknown); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationValidationFoundNoIssues); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); @@ -148,6 +158,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportResource); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitInstallDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyBegun); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyEnded); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListFirstApplied); @@ -155,6 +167,10 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListName); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListOrigin); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResultDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListUnit); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -1227,6 +1227,76 @@ namespace AppInstaller::CLI::Workflow return Utility::CaseInsensitiveStartsWith(identifierView, foldedInput); } + + Resource::LocString ToLocString(ConfigurationSetState state) + { + switch (state) + { + case ConfigurationSetState::Pending: + return Resource::String::ConfigurationSetStatePending; + case ConfigurationSetState::InProgress: + return Resource::String::ConfigurationSetStateInProgress; + case ConfigurationSetState::Completed: + return Resource::String::ConfigurationSetStateCompleted; + case ConfigurationSetState::Unknown: + default: + return Resource::String::ConfigurationSetStateUnknown; + } + } + + Resource::LocString ToLocString(ConfigurationUnitState state) + { + switch (state) + { + case ConfigurationUnitState::Pending: + return Resource::String::ConfigurationUnitStatePending; + case ConfigurationUnitState::InProgress: + return Resource::String::ConfigurationUnitStateInProgress; + case ConfigurationUnitState::Completed: + return Resource::String::ConfigurationUnitStateCompleted; + case ConfigurationUnitState::Skipped: + return Resource::String::ConfigurationUnitStateSkipped; + case ConfigurationUnitState::Unknown: + default: + return Resource::String::ConfigurationUnitStateUnknown; + } + } + + std::string_view ToString(ConfigurationChangeEventType type) + { + switch (type) + { + case ConfigurationChangeEventType::SetAdded: + return "SetAdded"; + case ConfigurationChangeEventType::SetStateChanged: + return "SetStateChanged"; + case ConfigurationChangeEventType::SetRemoved: + return "SetRemoved"; + case ConfigurationChangeEventType::Unknown: + default: + return "Unknown"; + } + } + + std::string_view ToString(ConfigurationUnitResultSource source) + { + switch (source) + { + case ConfigurationUnitResultSource::Internal: + return "Internal"; + case ConfigurationUnitResultSource::ConfigurationSet: + return "ConfigurationSet"; + case ConfigurationUnitResultSource::UnitProcessing: + return "UnitProcessing"; + case ConfigurationUnitResultSource::SystemState: + return "SystemState"; + case ConfigurationUnitResultSource::Precondition: + return "Precondition"; + case ConfigurationUnitResultSource::None: + default: + return "None"; + } + } } void CreateConfigurationProcessor(Context& context) @@ -1823,20 +1893,17 @@ namespace AppInstaller::CLI::Workflow } else { - TableOutput<4> historyTable{ context.Reporter, { Resource::String::ConfigureListIdentifier, Resource::String::ConfigureListName, Resource::String::ConfigureListFirstApplied, Resource::String::ConfigureListOrigin } }; + TableOutput<4> historyTable{ context.Reporter, { Resource::String::ConfigureListIdentifier, Resource::String::ConfigureListName, Resource::String::ConfigureListState, Resource::String::ConfigureListOrigin } }; for (const auto& set : history) { - std::ostringstream stream; - Utility::OutputTimePoint(stream, winrt::clock::to_sys(set.FirstApply())); - winrt::hstring origin = set.Path(); if (origin.empty()) { origin = set.Origin(); } - historyTable.OutputLine({ Utility::ConvertGuidToString(set.InstanceIdentifier()), Utility::ConvertToUTF8(set.Name()), std::move(stream).str(), Utility::ConvertToUTF8(origin)}); + historyTable.OutputLine({ Utility::ConvertGuidToString(set.InstanceIdentifier()), Utility::ConvertToUTF8(set.Name()), anon::ToLocString(set.State()), Utility::ConvertToUTF8(origin)}); } historyTable.Complete(); @@ -1897,18 +1964,132 @@ namespace AppInstaller::CLI::Workflow { const auto& set = context.Get<Data::ConfigurationContext>().Set(); - std::ostringstream stream; - Utility::OutputTimePoint(stream, winrt::clock::to_sys(set.FirstApply())); - + // Output a table with name/value pairs for some of the set's properties. Example: + // + // Field Value + // ---------------------------------------------------- + // Identifier {7D5CF50E-F3C6-4333-BFE6-5A806F9EBA4E} + // Name Test Name + // Origin Test Origin + // Path Test Path + // State Completed + // First Applied 2024-07-16 21:15:13.000 + // Apply Begun 2024-07-16 21:15:13.000 + // Apply Ended 2024-07-16 21:15:13.000 Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue }); table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListIdentifier }, Utility::ConvertGuidToString(set.InstanceIdentifier()) }); table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListName }, Utility::ConvertToUTF8(set.Name()) }); - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListFirstApplied }, std::move(stream).str() }); table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListOrigin }, Utility::ConvertToUTF8(set.Origin()) }); table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListPath }, Utility::ConvertToUTF8(set.Path()) }); + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListState }, anon::ToLocString(set.State()) }); + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListFirstApplied }, Utility::TimePointToString(winrt::clock::to_sys(set.FirstApply())) }); + + auto applyBegun = set.ApplyBegun(); + if (applyBegun != winrt::clock::time_point{}) + { + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyBegun }, Utility::TimePointToString(winrt::clock::to_sys(applyBegun)) }); + } + + auto applyEnded = set.ApplyEnded(); + if (applyEnded != winrt::clock::time_point{}) + { + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyEnded }, Utility::TimePointToString(winrt::clock::to_sys(applyEnded)) }); + } table.Complete(); + + context.Reporter.Info() << std::endl; + + // Output a table with unit state information. Groups are represented by indentation beneath their parent unit. Example: + // + // Unit State Result Details + // ------------------------------------------------------------ + // Module/Resource [Name] Completed 0x00000000 + // Module2/Resource [Group] Completed 0x00000000 + // |-Module3/Resource [Child1] Completed 0x00000000 + // |---Module4/Resource2 Completed 0x80004005 I failed :( + // |-Module3/Resource [Child2] Completed 0x00000000 + Execution::TableOutput<4> unitTable(context.Reporter, { Resource::String::ConfigureListUnit, Resource::String::ConfigureListState, Resource::String::ConfigureListResult, Resource::String::ConfigureListResultDescription }); + + struct UnitSiblings + { + size_t Depth = 0; + size_t Current = 0; + std::vector<ConfigurationUnit> Siblings; + }; + + std::vector<UnitSiblings> stack; + + { + UnitSiblings initial; + auto units = set.Units(); + initial.Siblings.resize(units.Size()); + units.GetMany(0, initial.Siblings); + stack.emplace_back(std::move(initial)); + } + + // Each item on the stack is a list of sibling units. + // Each iteration, we process the Current sibling from the group on top of the stack. + // If it is a group, we add its children as a new stack item to be processed next. + while (!stack.empty()) + { + UnitSiblings& currentSiblings = stack.back(); + + if (currentSiblings.Current >= currentSiblings.Siblings.size()) + { + stack.pop_back(); + continue; + } + + ConfigurationUnit& currentUnit = currentSiblings.Siblings[currentSiblings.Current++]; + + std::ostringstream unitStream; + + if (currentSiblings.Depth) + { + unitStream << '|' << std::string((currentSiblings.Depth * 2) - 1, '-'); + } + + unitStream << Utility::ConvertToUTF8(currentUnit.Type()); + + auto identifier = currentUnit.Identifier(); + if (!identifier.empty()) + { + unitStream << " [" << Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(identifier)) << ']'; + } + + auto resultInformation = currentUnit.ResultInformation(); + std::ostringstream resultStream; + std::string resultDetails; + + if (resultInformation) + { + resultStream << "0x" << Logging::SetHRFormat << resultInformation.ResultCode(); + + auto description = resultInformation.Description(); + if (description.empty()) + { + description = resultInformation.Details(); + } + + resultDetails = Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(description)); + } + + unitTable.OutputLine({ std::move(unitStream).str(), anon::ToLocString(currentUnit.State()), std::move(resultStream).str(), std::move(resultDetails) }); + + if (currentUnit.IsGroup()) + { + UnitSiblings unitChildren; + unitChildren.Depth = currentSiblings.Depth + 1; + auto units = currentUnit.Units(); + unitChildren.Siblings.resize(units.Size()); + units.GetMany(0, unitChildren.Siblings); + stack.emplace_back(std::move(unitChildren)); + } + } + + unitTable.Complete(); } void CompleteConfigurationHistoryItem(Execution::Context& context) @@ -1935,4 +2116,69 @@ namespace AppInstaller::CLI::Workflow } } } + + void MonitorConfigurationStatus(Execution::Context& context) + { + auto& configurationContext = context.Get<Data::ConfigurationContext>(); + + std::mutex activeSetMutex; + ConfigurationSet activeSet{ nullptr }; + decltype(activeSet.ConfigurationSetChange(winrt::auto_revoke, nullptr)) activeSetRevoker; + + auto setChangeHandler = [&](const ConfigurationSet& set, const ConfigurationSetChangeData& changeData) + { + if (changeData.Change() == ConfigurationSetChangeEventType::SetStateChanged) + { + context.Reporter.Info() << "(SetStateChanged) " << set.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.SetState()) << std::endl; + } + else if (changeData.Change() == ConfigurationSetChangeEventType::UnitStateChanged) + { + context.Reporter.Info() << "(UnitStateChanged) " << changeData.Unit().InstanceIdentifier() << " :: " << anon::ToLocString(changeData.UnitState()) << std::endl; + + auto resultInformation = changeData.ResultInformation(); + if (resultInformation) + { + context.Reporter.Info() << " [" << anon::ToString(resultInformation.ResultSource()) << "] :: 0x" << Logging::SetHRFormat << resultInformation.ResultCode() << std::endl; + } + } + }; + + auto setActiveSet = [&](const ConfigurationSet& set, bool force) + { + std::lock_guard<std::mutex> lock{ activeSetMutex }; + + if (force || !activeSet) + { + activeSet = set; + activeSetRevoker = activeSet.ConfigurationSetChange(winrt::auto_revoke, setChangeHandler); + } + }; + + auto processorRevoker = configurationContext.Processor().ConfigurationChange(winrt::auto_revoke, [&](const ConfigurationSet& set, const ConfigurationChangeData& changeData) + { + context.Reporter.Info() << '[' << anon::ToString(changeData.Change()) << "] " << changeData.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.State()) << std::endl; + + if (changeData.Change() == ConfigurationChangeEventType::SetStateChanged && changeData.State() == ConfigurationSetState::InProgress) + { + setActiveSet(set, true); + } + }); + + for (ConfigurationSet& historySet : configurationContext.History()) + { + if (historySet.State() == ConfigurationSetState::InProgress) + { + setActiveSet(historySet, false); + } + } + + for (;;) + { + std::this_thread::sleep_for(250ms); + if (context.IsTerminated()) + { + return; + } + } + } } diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h @@ -150,4 +150,10 @@ namespace AppInstaller::CLI::Workflow // Inputs: None // Outputs: None void CompleteConfigurationHistoryItem(Execution::Context& context); + + // Monitors configuration status. + // Required Args: None + // Inputs: None + // Outputs: None + void MonitorConfigurationStatus(Execution::Context& context); } diff --git a/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs @@ -93,7 +93,7 @@ namespace AppInstallerCLIE2ETests TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); WinGetSettingsHelper.ConfigureFeature("configuration03", true); - var result = TestCommon.RunAICLICommand("configure show", TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo_0_3.yml")); + var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo_0_3.yml")} --verbose"); Assert.AreEqual(0, result.ExitCode); Assert.True(result.StdOut.Contains(Constants.TestRepoName)); } diff --git a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs @@ -237,9 +237,12 @@ namespace AppInstallerCLIE2ETests.Helpers private static void SetWingetSettings(JObject settingsJson) { var forcedExperimentalFeatures = ForcedExperimentalFeatures; - foreach (var feature in forcedExperimentalFeatures) + if (forcedExperimentalFeatures != null) { - ConfigureFeature(settingsJson, feature, true); + foreach (var feature in forcedExperimentalFeatures) + { + ConfigureFeature(settingsJson, feature, true); + } } SetWingetSettings(settingsJson.ToString()); diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -3045,4 +3045,65 @@ Please specify one of them using the --source option to proceed.</value> <data name="WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND" xml:space="preserve"> <value>The specified configuration could not be found.</value> </data> + <data name="ConfigurationSetStateCompleted" xml:space="preserve"> + <value>Completed</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationSetStateInProgress" xml:space="preserve"> + <value>In progress</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationSetStatePending" xml:space="preserve"> + <value>Pending</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationSetStateUnknown" xml:space="preserve"> + <value>Unknown</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationStatusWatchArgumentDescription" xml:space="preserve"> + <value>Monitor configuration status.</value> + <comment>As in "to monitor the status of a configuration being applied".</comment> + </data> + <data name="ConfigurationUnitStateCompleted" xml:space="preserve"> + <value>Completed</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationUnitStateInProgress" xml:space="preserve"> + <value>In progress</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationUnitStatePending" xml:space="preserve"> + <value>Pending</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationUnitStateSkipped" xml:space="preserve"> + <value>Skipped</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigurationUnitStateUnknown" xml:space="preserve"> + <value>Unknown</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigureListApplyBegun" xml:space="preserve"> + <value>Apply Started</value> + <comment>When the configuration application started.</comment> + </data> + <data name="ConfigureListApplyEnded" xml:space="preserve"> + <value>Apply Ended</value> + <comment>When the configuration application ended.</comment> + </data> + <data name="ConfigureListResult" xml:space="preserve"> + <value>Result</value> + </data> + <data name="ConfigureListResultDescription" xml:space="preserve"> + <value>Details</value> + </data> + <data name="ConfigureListState" xml:space="preserve"> + <value>State</value> + <comment>The state of processing an item.</comment> + </data> + <data name="ConfigureListUnit" xml:space="preserve"> + <value>Unit</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/SQLiteDynamicStorage.cpp b/src/AppInstallerCLITests/SQLiteDynamicStorage.cpp @@ -20,7 +20,7 @@ TEST_CASE("SQLiteDynamicStorage_UpgradeDetection", "[sqlite_dynamic]") SQLiteDynamicStorage storage{ tempFile.GetPath(), Version{ 1, 0 } }; { - auto transactionLock = storage.TryBeginTransaction("test"); + auto transactionLock = storage.TryBeginTransaction("test", false); REQUIRE(transactionLock); } @@ -33,11 +33,11 @@ TEST_CASE("SQLiteDynamicStorage_UpgradeDetection", "[sqlite_dynamic]") REQUIRE(storage.GetVersion() == Version{ 1, 0 }); - auto transactionLock = storage.TryBeginTransaction("test"); + auto transactionLock = storage.TryBeginTransaction("test", false); REQUIRE(!transactionLock); REQUIRE(storage.GetVersion() == Version{ 2, 0 }); - transactionLock = storage.TryBeginTransaction("test"); + transactionLock = storage.TryBeginTransaction("test", false); REQUIRE(transactionLock); } diff --git a/src/AppInstallerCLITests/SQLiteWrapper.cpp b/src/AppInstallerCLITests/SQLiteWrapper.cpp @@ -729,3 +729,128 @@ TEST_CASE("SQLBuilder_InsertValueBinding", "[sqlbuilder]") REQUIRE(!select.Step()); } } + +TEST_CASE("SQLiteWrapperTransactionRollback", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Rollback(); + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperTransactionRollbackOnDestruct", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperTransactionCommit", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperTransactionImmediate", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", true); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperTransactionWriteConflict", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + connection.SetJournalMode("WAL"); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Connection connection2 = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + std::chrono::milliseconds busyWait = 250ms; + connection2.SetBusyTimeout(busyWait); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", true); + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + // Start second transaction + std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); + std::chrono::system_clock::time_point end = start; + try + { + Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); + } + catch (...) + { + end = std::chrono::system_clock::now(); + } + + std::chrono::milliseconds duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + REQUIRE(duration >= busyWait); + + transaction.Commit(); + + Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); + InsertIntoSimpleTestTable(connection2, firstVal, secondVal); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp @@ -213,7 +213,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 } else { - builder.LiteralColumn(""); + builder.Value(std::string_view{}); } builder.As(valueAlias).From(s_ManifestTable_Table_Name); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.cpp @@ -227,7 +227,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V2_0 // where table.value = <value1> and paired.pairedValue = <value2> builder.Select(). Column(QCol(tableName, s_SystemReferenceStringTable_PrimaryName)).As(primaryAlias). - LiteralColumn("").As(valueAlias). + Value(std::string_view{}).As(valueAlias). From(tableName). Join(pairedTableName).On(QCol(tableName, s_SystemReferenceStringTable_PrimaryName), QCol(pairedTableName, s_SystemReferenceStringTable_PrimaryName)). Where(QCol(tableName, valueName)); diff --git a/src/AppInstallerSharedLib/DateTime.cpp b/src/AppInstallerSharedLib/DateTime.cpp @@ -37,6 +37,13 @@ namespace AppInstaller::Utility } } + std::string TimePointToString(const std::chrono::system_clock::time_point& time, bool useRFC3339) + { + std::ostringstream stream; + OutputTimePoint(stream, time, useRFC3339); + return std::move(stream).str(); + } + std::string GetCurrentTimeForFilename() { std::stringstream stream; diff --git a/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h b/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h @@ -12,6 +12,9 @@ namespace AppInstaller::Utility // Time is also assumed to be after the epoch. void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, bool useRFC3339 = false); + // Converts the time point to a string using OutputTimePoint. + std::string TimePointToString(const std::chrono::system_clock::time_point& time, bool useRFC3339 = false); + // Gets the current time as a string. Can be used as a file name. std::string GetCurrentTimeForFilename(); diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteDynamicStorage.h b/src/AppInstallerSharedLib/Public/winget/SQLiteDynamicStorage.h @@ -34,7 +34,7 @@ namespace AppInstaller::SQLite TransactionLock(std::mutex& mutex); _Acquires_lock_(mutex) - TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name); + TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name, bool immediateWrite); // Abandons the transaction and any changes; releases the connection lock. void Rollback(bool throwOnError = true); @@ -44,12 +44,12 @@ namespace AppInstaller::SQLite private: std::lock_guard<std::mutex> m_lock; - Savepoint m_transaction; + Transaction m_transaction; }; // Acquires the connection lock and begins a transaction on the database. // If the returned result is empty, the schema version has changed and the caller must handle this. - std::unique_ptr<TransactionLock> TryBeginTransaction(std::string_view name); + std::unique_ptr<TransactionLock> TryBeginTransaction(std::string_view name, bool immediateWrite); // Locks the connection for use during the schema upgrade. std::unique_ptr<TransactionLock> LockConnection(); diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h @@ -282,6 +282,7 @@ namespace AppInstaller::SQLite::Builder StatementBuilder& Equals(details::unbound_t, std::optional<size_t> index = {}); StatementBuilder& Equals(std::nullptr_t); StatementBuilder& Equals(); + StatementBuilder& Equals(const QualifiedColumn& column); template <typename ValueType> StatementBuilder& IsGreaterThan(const ValueType& value) @@ -302,8 +303,6 @@ namespace AppInstaller::SQLite::Builder StatementBuilder& LikeWithEscape(std::string_view value); StatementBuilder& Like(details::unbound_t); - StatementBuilder& LiteralColumn(std::string_view value); - StatementBuilder& Escape(std::string_view escapeChar); StatementBuilder& Not(); @@ -380,6 +379,17 @@ namespace AppInstaller::SQLite::Builder StatementBuilder& Column(const details::SubBuilder& column); StatementBuilder& EndColumns(); + // Set the columns null constraint. + StatementBuilder& NotNull(bool isTrue = true); + + // Set the column's default value. + template <typename ValueType> + StatementBuilder& Default(const ValueType& value) + { + m_stream << " DEFAULT (" << value << ")"; + return *this; + } + // Add the values clause for an insert statement. template <typename... ValueTypes> StatementBuilder& Values(const ValueTypes&... values) diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteWrapper.h b/src/AppInstallerSharedLib/Public/winget/SQLiteWrapper.h @@ -377,6 +377,39 @@ namespace AppInstaller::SQLite State m_state = State::Prepared; }; + // A SQLite transaction. + // Use as the beginning of a transaction stack, specifically when the transaction will write + // and the database is in WAL mode. + struct Transaction + { + // Creates a transaction, beginning it. + static Transaction Create(Connection& connection, std::string name, bool immediateWrite); + + Transaction(); + + Transaction(const Transaction&) = delete; + Transaction& operator=(const Transaction&) = delete; + + Transaction(Transaction&&) = default; + Transaction& operator=(Transaction&&) = default; + + ~Transaction(); + + // Rolls back the Transaction. + void Rollback(bool throwOnError = true); + + // Commits the Transaction. + void Commit(); + + private: + Transaction(Connection& connection, std::string&& name, bool immediateWrite); + + std::string m_name; + DestructionToken m_inProgress = true; + Statement m_rollback; + Statement m_commit; + }; + // A SQLite savepoint. struct Savepoint { diff --git a/src/AppInstallerSharedLib/SQLiteDynamicStorage.cpp b/src/AppInstallerSharedLib/SQLiteDynamicStorage.cpp @@ -54,10 +54,10 @@ namespace AppInstaller::SQLite } _Acquires_lock_(mutex) - SQLiteDynamicStorage::TransactionLock::TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name) : + SQLiteDynamicStorage::TransactionLock::TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name, bool immediateWrite) : m_lock(mutex) { - m_transaction = Savepoint::Create(connection, std::string{ name }); + m_transaction = Transaction::Create(connection, std::string{ name }, immediateWrite); } void SQLiteDynamicStorage::TransactionLock::Rollback(bool throwOnError) @@ -70,9 +70,9 @@ namespace AppInstaller::SQLite m_transaction.Commit(); } - std::unique_ptr<SQLiteDynamicStorage::TransactionLock> SQLiteDynamicStorage::TryBeginTransaction(std::string_view name) + std::unique_ptr<SQLiteDynamicStorage::TransactionLock> SQLiteDynamicStorage::TryBeginTransaction(std::string_view name, bool immediateWrite) { - auto result = std::make_unique<TransactionLock>(*m_interfaceLock, m_dbconn, name); + auto result = std::make_unique<TransactionLock>(*m_interfaceLock, m_dbconn, name, immediateWrite); Version currentVersion = Version::GetSchemaVersion(m_dbconn); if (currentVersion != m_version) diff --git a/src/AppInstallerSharedLib/SQLiteStatementBuilder.cpp b/src/AppInstallerSharedLib/SQLiteStatementBuilder.cpp @@ -358,6 +358,12 @@ namespace AppInstaller::SQLite::Builder return *this; } + StatementBuilder& StatementBuilder::Equals(const QualifiedColumn& column) + { + OutputColumns(m_stream, " = ", column); + return *this; + } + StatementBuilder& StatementBuilder::IsGreaterThan(details::unbound_t, std::optional<size_t> index) { AppendOpAndBinder(Op::GreaterThan, index); @@ -382,17 +388,6 @@ namespace AppInstaller::SQLite::Builder return *this; } - StatementBuilder& StatementBuilder::LiteralColumn(std::string_view value) - { - if (m_needsComma) - { - m_stream << ", "; - } - AddBindFunctor(AppendOpAndBinder(Op::Literal), value); - m_needsComma = true; - return *this; - } - StatementBuilder& StatementBuilder::Escape(std::string_view escapeChar) { THROW_HR_IF(E_INVALIDARG, escapeChar.length() != 1); @@ -679,6 +674,15 @@ namespace AppInstaller::SQLite::Builder return *this; } + StatementBuilder& StatementBuilder::NotNull(bool isTrue) + { + if (isTrue) + { + m_stream << " NOT NULL"; + } + return *this; + } + StatementBuilder& StatementBuilder::BeginValues() { m_stream << " VALUES ("; diff --git a/src/AppInstallerSharedLib/SQLiteWrapper.cpp b/src/AppInstallerSharedLib/SQLiteWrapper.cpp @@ -374,6 +374,67 @@ namespace AppInstaller::SQLite m_state = State::Prepared; } + Transaction::Transaction() : m_inProgress(false) + {} + + Transaction::Transaction(Connection& connection, std::string&& name, bool immediateWrite) : + m_name(std::move(name)) + { + using namespace std::string_literals; + + Statement begin = Statement::Create(connection, "BEGIN "s + (immediateWrite ? "IMMEDIATE" : "DEFERRED")); + m_rollback = Statement::Create(connection, "ROLLBACK"); + m_commit = Statement::Create(connection, "COMMIT"); + + AICLI_LOG(SQL, Verbose, << "Begin transaction: " << m_name); + begin.Step(); + } + + Transaction Transaction::Create(Connection& connection, std::string name, bool immediateWrite) + { + return { connection, std::move(name), immediateWrite }; + } + + Transaction::~Transaction() + { + // Prevent a termination by not throwing on errors here + Rollback(false); + } + + void Transaction::Rollback(bool throwOnError) + { + if (m_inProgress) + { + // Only try rollback once + m_inProgress = false; + + try + { + AICLI_LOG(SQL, Verbose, << "Roll back transaction: " << m_name); + m_rollback.Step(true); + } + catch (...) + { + if (throwOnError) + { + throw; + } + + LOG_CAUGHT_EXCEPTION(); + } + } + } + + void Transaction::Commit() + { + if (m_inProgress) + { + AICLI_LOG(SQL, Verbose, << "Commit transaction: " << m_name); + m_commit.Step(); + m_inProgress = false; + } + } + Savepoint::Savepoint() : m_inProgress(false) {} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs @@ -23,6 +23,10 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers { // To run the tests OOP, you need to replace Microsoft.Management.Configuration.dll with Microsoft.Management.Configuration.OutOfProc.dll (renamed to remove the OutOfProc). // You will also need to copy over Microsoft.Management.Configuration.winmd as it is needed by COM. + // + // You can use the script to do this: + // <git root>\src\Microsoft.Management.Configuration.OutOfProc\Prepare-ConfigurationOOPTests.ps1 -BuildOutputPath <git root>\src\x64\Debug + // // It can be easier to run the tests on the command line because any changes needing a recompile will overwrite the DLL update above. // The test runner is located somewhere like this: // C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcDiscoverer.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcDiscoverer.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="OutOfProcDiscoverer.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="TestConfigurationSetProcessor.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -42,6 +42,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers new Dictionary<ConfigurationUnit, Exception>(); /// <summary> + /// Gets or sets a value indicating whether the default unit processors for groups will enable group processing. + /// </summary> + internal bool EnableDefaultGroupProcessorCreation { get; set; } = false; + + /// <summary> /// Gets the ConfigurationSet that this processor targets. /// </summary> protected ConfigurationSet? Set { get; private set; } @@ -60,7 +65,14 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers if (!this.Processors.ContainsKey(unit)) { - this.Processors.Add(unit, new TestConfigurationUnitProcessor(unit)); + if (this.EnableDefaultGroupProcessorCreation && unit.IsGroup) + { + this.Processors.Add(unit, new TestConfigurationUnitGroupProcessor(unit)); + } + else + { + this.Processors.Add(unit, new TestConfigurationUnitProcessor(unit)); + } } return this.Processors[unit]; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationHistoryTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationHistoryTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests /// Unit tests for configuration history. /// </summary> [Collection("UnitTestCollection")] - [OutOfProc] + [InProc] public class ConfigurationHistoryTests : ConfigurationProcessorTestBase { /// <summary> @@ -38,6 +38,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests /// Checks that the history matches the applied set. /// </summary> [Fact] + [OutOfProc] public void ApplySet_HistoryMatches_0_1() { this.RunApplyHistoryMatchTest( @@ -79,6 +80,7 @@ properties: /// Checks that the history matches the applied set. /// </summary> [Fact] + [OutOfProc] public void ApplySet_HistoryMatches_0_2() { this.RunApplyHistoryMatchTest( @@ -168,13 +170,14 @@ resources: i: '7' j: 8 q: 42 -", new string[] { "AssertIdentifier2" }); +"); } /// <summary> /// Applies a set, reads the history, changes the read set and reapplies it. /// </summary> [Fact] + [OutOfProc] public void ApplySet_ChangeHistory() { string disabledIdentifier = "AssertIdentifier2"; @@ -241,6 +244,7 @@ properties: /// Applies a set, reads the history and removes it. /// </summary> [Fact] + [OutOfProc] public void ApplySet_RemoveHistory() { ConfigurationSet returnedSet = this.RunApplyHistoryMatchTest( @@ -301,6 +305,9 @@ properties: ConfigurationSet configurationSet = configurationSetResult.Set; Assert.NotNull(configurationSet); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.EnableDefaultGroupProcessorCreation = true; + configurationSet.Name = "Test Name"; configurationSet.Origin = "Test Origin"; configurationSet.Path = "Test Path"; @@ -334,6 +341,7 @@ properties: } this.AssertSetsEqual(configurationSet, historySet); + this.AssertResultsEqual(result, historySet); return historySet; } @@ -343,7 +351,15 @@ properties: Assert.Equal(expectedSet.Name, actualSet.Name); Assert.Equal(expectedSet.Origin, actualSet.Origin); Assert.Equal(expectedSet.Path, actualSet.Path); - Assert.NotEqual(DateTimeOffset.UnixEpoch, actualSet.FirstApply); + + Assert.Equal(ConfigurationSetState.Completed, actualSet.State); + + this.AssertTimeNotZero(actualSet.FirstApply); + this.AssertTimeNotZero(actualSet.ApplyBegun); + this.AssertTimeNotZero(actualSet.ApplyEnded); + Assert.True(actualSet.FirstApply <= actualSet.ApplyBegun); + Assert.True(actualSet.ApplyBegun <= actualSet.ApplyEnded); + Assert.Equal(expectedSet.SchemaVersion, actualSet.SchemaVersion); Assert.Equal(expectedSet.SchemaUri, actualSet.SchemaUri); Assert.True(expectedSet.Metadata.ContentEquals(actualSet.Metadata)); @@ -351,6 +367,12 @@ properties: this.AssertUnitsListEqual(expectedSet.Units, actualSet.Units); } + private void AssertTimeNotZero(DateTimeOffset actualTime) + { + Assert.NotEqual(DateTimeOffset.UnixEpoch, actualTime); + Assert.NotEqual(DateTimeOffset.MinValue, actualTime); + } + private void AssertUnitsListEqual(IList<ConfigurationUnit> expectedUnits, IList<ConfigurationUnit> actualUnits) { Assert.Equal(expectedUnits.Count, actualUnits.Count); @@ -387,5 +409,64 @@ properties: this.AssertUnitsListEqual(expectedUnit.Units, actualUnit.Units); } } + + private void AssertResultsEqual(ApplyConfigurationSetResult expected, ConfigurationSet actualSet) + { + List<ConfigurationUnit> actualUnitList = new List<ConfigurationUnit>(); + + foreach (ConfigurationUnit unit in actualSet.Units) + { + this.AccumulateUnits(actualUnitList, unit); + } + + foreach (ApplyConfigurationUnitResult expectedUnitResult in expected.UnitResults) + { + ConfigurationUnit? actualUnit = null; + foreach (ConfigurationUnit historyUnit in actualUnitList) + { + if (historyUnit.InstanceIdentifier == expectedUnitResult.Unit.InstanceIdentifier) + { + actualUnit = historyUnit; + } + } + + this.AssertUnitResultsEqual(expectedUnitResult, actualUnit); + } + } + + private void AccumulateUnits(List<ConfigurationUnit> unitList, ConfigurationUnit unit) + { + unitList.Add(unit); + if (unit.IsGroup) + { + foreach (ConfigurationUnit child in unit.Units) + { + this.AccumulateUnits(unitList, child); + } + } + } + + private void AssertUnitResultsEqual(ApplyConfigurationUnitResult expectedResult, ConfigurationUnit? actualUnit) + { + Assert.NotNull(actualUnit); + Assert.Equal(expectedResult.State, actualUnit.State); + + var expectedResultInformation = expectedResult.ResultInformation; + if (expectedResultInformation != null) + { + var actualResultInformation = actualUnit.ResultInformation; + Assert.NotNull(actualResultInformation); + + Assert.Equal(expectedResultInformation.ResultCode == null, actualResultInformation.ResultCode == null); + if (expectedResultInformation.ResultCode != null) + { + Assert.Equal(expectedResultInformation.ResultCode.HResult, actualResultInformation.ResultCode!.HResult); + } + + Assert.Equal(expectedResultInformation.Description, actualResultInformation.Description); + Assert.Equal(expectedResultInformation.Details, actualResultInformation.Details); + Assert.Equal(expectedResultInformation.ResultSource, actualResultInformation.ResultSource); + } + } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorApplyTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorApplyTests.cs @@ -418,6 +418,12 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.SetStateChanged, SetState = ConfigurationSetState.Completed }, }; + // Drop the pending event if it happens to be present + if (progressEvents.Count > 0 && progressEvents[0].Change == ConfigurationSetChangeEventType.SetStateChanged && progressEvents[0].SetState == ConfigurationSetState.Pending) + { + progressEvents.RemoveAt(0); + } + Assert.Equal(expectedProgress.Count(), progressEvents.Count); for (int i = 0; i < progressEvents.Count; ++i) @@ -531,6 +537,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests waitingUnitApply.Set(); WaitOn(waitingProgress); Assert.Equal(ConfigurationSetState.Completed, progressState); + + waitingSetOperation.AsTask().Wait(); } /// <summary> diff --git a/src/Microsoft.Management.Configuration/ConfigurationChangeData.h b/src/Microsoft.Management.Configuration/ConfigurationChangeData.h @@ -20,7 +20,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: ConfigurationChangeEventType m_change{}; - guid m_instanceIdentifier; + guid m_instanceIdentifier{}; ConfigurationSetState m_state{}; #endif }; diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp @@ -20,6 +20,7 @@ #include "GetConfigurationSetDetailsResult.h" #include "DefaultSetGroupProcessor.h" #include "ConfigurationSequencer.h" +#include "ConfigurationStatus.h" #include <AppInstallerErrors.h> #include <AppInstallerStrings.h> @@ -184,15 +185,32 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::ignore = m_threadGlobals.GetTelemetryLogger().EnableRuntime(value); } - event_token ConfigurationProcessor::ConfigurationChange(const Windows::Foundation::TypedEventHandler<ConfigurationSet, ConfigurationChangeData>& handler) + event_token ConfigurationProcessor::ConfigurationChange(const Windows::Foundation::TypedEventHandler<Configuration::ConfigurationSet, Configuration::ConfigurationChangeData>& handler) { + if (!m_configurationChange) + { + auto status = ConfigurationStatus::Instance(); + std::atomic_store(&m_changeRegistration, status->RegisterForChange(*this)); + } + return m_configurationChange.add(handler); } void ConfigurationProcessor::ConfigurationChange(const event_token& token) noexcept { m_configurationChange.remove(token); + + if (!m_configurationChange) + { + std::atomic_store(&m_changeRegistration, {}); + } + } + + void ConfigurationProcessor::ConfigurationChange(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data) try + { + m_configurationChange(set, data); } + CATCH_LOG(); Windows::Foundation::Collections::IVector<Configuration::ConfigurationSet> ConfigurationProcessor::GetConfigurationHistory() { @@ -311,7 +329,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } Windows::Foundation::Collections::IVector<ConfigurationConflict> ConfigurationProcessor::CheckForConflicts( - const Windows::Foundation::Collections::IVectorView<ConfigurationSet>& configurationSets, + const Windows::Foundation::Collections::IVectorView<Configuration::ConfigurationSet>& configurationSets, bool includeConfigurationHistory) { UNREFERENCED_PARAMETER(configurationSets); @@ -320,24 +338,26 @@ namespace winrt::Microsoft::Management::Configuration::implementation } Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<ConfigurationConflict>> ConfigurationProcessor::CheckForConflictsAsync( - const Windows::Foundation::Collections::IVectorView<ConfigurationSet>& configurationSets, + const Windows::Foundation::Collections::IVectorView<Configuration::ConfigurationSet>& configurationSets, bool includeConfigurationHistory) { co_return CheckForConflicts(configurationSets, includeConfigurationHistory); } - Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags) + Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetails(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); return GetSetDetailsImpl(configurationSet, detailFlags); } - Windows::Foundation::IAsyncOperationWithProgress<Configuration::GetConfigurationSetDetailsResult, Configuration::GetConfigurationUnitDetailsResult> ConfigurationProcessor::GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags) + Windows::Foundation::IAsyncOperationWithProgress<Configuration::GetConfigurationSetDetailsResult, Configuration::GetConfigurationUnitDetailsResult> ConfigurationProcessor::GetSetDetailsAsync( + const Configuration::ConfigurationSet& configurationSet, + ConfigurationUnitDetailFlags detailFlags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); auto strong_this{ get_strong() }; - ConfigurationSet localSet = configurationSet; + Configuration::ConfigurationSet localSet = configurationSet; co_await winrt::resume_background(); @@ -351,7 +371,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_database.EnsureOpened(false); cancellation.ThrowIfCancelled(); - std::vector<ConfigurationSet> result; + std::vector<Configuration::ConfigurationSet> result; for (const auto& set : m_database.GetSetHistory()) { PropagateLifetimeWatcher(*set); @@ -362,7 +382,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetailsImpl( - const ConfigurationSet& configurationSet, + const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags, AppInstaller::WinRT::AsyncProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> progress) { @@ -443,18 +463,20 @@ namespace winrt::Microsoft::Management::Configuration::implementation return *unitResult; } - Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySet(const ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags) + Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySet(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); return ApplySetImpl(configurationSet, flags); } - Windows::Foundation::IAsyncOperationWithProgress<Configuration::ApplyConfigurationSetResult, Configuration::ConfigurationSetChangeData> ConfigurationProcessor::ApplySetAsync(const ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags) + Windows::Foundation::IAsyncOperationWithProgress<Configuration::ApplyConfigurationSetResult, Configuration::ConfigurationSetChangeData> ConfigurationProcessor::ApplySetAsync( + const Configuration::ConfigurationSet& configurationSet, + ApplyConfigurationSetFlags flags) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); auto strong_this{ get_strong() }; - ConfigurationSet localSet = configurationSet; + Configuration::ConfigurationSet localSet = configurationSet; co_await winrt::resume_background(); @@ -462,13 +484,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation } Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySetImpl( - const ConfigurationSet& configurationSet, + const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags, AppInstaller::WinRT::AsyncProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> progress) { auto threadGlobals = m_threadGlobals.SetForCurrentThread(); IConfigurationGroupProcessor groupProcessor; + bool recordHistoryAndStatus = false; if (WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) { @@ -483,6 +506,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Write this set to the database history // This is a somewhat arbitrary time to write it, but it should not be done if PerformConsistencyCheckOnly is passed, so this is convenient. + recordHistoryAndStatus = true; m_database.EnsureOpened(); progress.ThrowIfCancelled(); m_database.WriteSetHistory(configurationSet, WI_IsFlagSet(flags, ApplyConfigurationSetFlags::DoNotOverwriteMatchingOriginSet)); @@ -522,28 +546,34 @@ namespace winrt::Microsoft::Management::Configuration::implementation try { ConfigurationSequencer sequencer{ m_database }; - - if (!WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) - { - if (sequencer.Enqueue(configurationSet)) + auto status = ConfigurationStatus::Instance(); + guid setInstanceIdentifier = configurationSet.InstanceIdentifier(); + auto updateState = [&](ConfigurationSetState state) { try { - progress.Progress(implementation::ConfigurationSetChangeData::Create(ConfigurationSetState::Pending)); + progress.Progress(implementation::ConfigurationSetChangeData::Create(state)); } CATCH_LOG(); + if (recordHistoryAndStatus) + { + status->UpdateSetState(setInstanceIdentifier, state); + } + }; + + if (!WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) + { + if (sequencer.Enqueue(configurationSet)) + { + updateState(ConfigurationSetState::Pending); sequencer.Wait(progress); } } progress.ThrowIfCancelled(); - try - { - progress.Progress(implementation::ConfigurationSetChangeData::Create(ConfigurationSetState::InProgress)); - } - CATCH_LOG(); + updateState(ConfigurationSetState::InProgress); // Forward unit result progress to caller auto applyOperation = groupProcessor.ApplyGroupSettingsAsync([&](const auto&, const IApplyGroupMemberSettingsResult& unitResult) @@ -555,9 +585,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation } // Create progress object - auto applyResult = make_self<wil::details::module_count_wrapper<implementation::ConfigurationSetChangeData>>(); + auto applyResult = make_self<implementation::ConfigurationSetChangeData>(); applyResult->Initialize(unitResult); progress.Progress(*applyResult); + + if (recordHistoryAndStatus) + { + status->UpdateUnitState(setInstanceIdentifier, applyResult); + } }); // Cancel the inner operation if we are cancelled @@ -590,11 +625,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation itr->second->ResultInformation()); } - try - { - progress.Progress(implementation::ConfigurationSetChangeData::Create(ConfigurationSetState::Completed)); - } - CATCH_LOG(); + updateState(ConfigurationSetState::Completed); m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForApply(*winrt::get_self<implementation::ConfigurationSet>(configurationSet), *result); return *result; @@ -609,18 +640,18 @@ namespace winrt::Microsoft::Management::Configuration::implementation } } - Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSet(const ConfigurationSet& configurationSet) + Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSet(const Configuration::ConfigurationSet& configurationSet) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); return TestSetImpl(configurationSet); } - Windows::Foundation::IAsyncOperationWithProgress<Configuration::TestConfigurationSetResult, Configuration::TestConfigurationUnitResult> ConfigurationProcessor::TestSetAsync(const ConfigurationSet& configurationSet) + Windows::Foundation::IAsyncOperationWithProgress<Configuration::TestConfigurationSetResult, Configuration::TestConfigurationUnitResult> ConfigurationProcessor::TestSetAsync(const Configuration::ConfigurationSet& configurationSet) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); auto strong_this{ get_strong() }; - ConfigurationSet localSet = configurationSet; + Configuration::ConfigurationSet localSet = configurationSet; co_await winrt::resume_background(); @@ -628,7 +659,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSetImpl( - const ConfigurationSet& configurationSet, + const Configuration::ConfigurationSet& configurationSet, AppInstaller::WinRT::AsyncProgress<TestConfigurationSetResult, TestConfigurationUnitResult> progress) { auto threadGlobals = m_threadGlobals.SetForCurrentThread(); @@ -817,7 +848,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation return *result; } - IConfigurationGroupProcessor ConfigurationProcessor::GetSetGroupProcessor(const ConfigurationSet& configurationSet) + IConfigurationGroupProcessor ConfigurationProcessor::GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet) { IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); @@ -881,12 +912,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_supportSchema03 = value; } - void ConfigurationProcessor::RemoveHistory(const ConfigurationSet& configurationSet) - { - m_database.EnsureOpened(false); - m_database.RemoveSetHistory(configurationSet); - } - void ConfigurationProcessor::SendDiagnosticsImpl(const IDiagnosticInformation& information) { std::lock_guard<std::recursive_mutex> lock{ m_diagnosticsMutex }; diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationProcessor.h @@ -6,6 +6,7 @@ #include <winrt/Windows.Foundation.Collections.h> #include <winrt/Windows.Storage.Streams.h> #include "ConfigThreadGlobals.h" +#include "ConfigurationStatus.h" #include "Database/ConfigurationDatabase.h" #include <winget/AsyncTokens.h> #include <winget/ILifetimeWatcher.h> @@ -18,7 +19,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation { struct ConfigurationProcessor : ConfigurationProcessorT<ConfigurationProcessor, winrt::cloaked<AppInstaller::WinRT::ILifetimeWatcher>>, AppInstaller::WinRT::LifetimeWatcherBase { - using ConfigurationSet = Configuration::ConfigurationSet; using ConfigurationSetChangeData = Configuration::ConfigurationSetChangeData; using ConfigurationUnit = Configuration::ConfigurationUnit; using ApplyConfigurationSetResult = Configuration::ApplyConfigurationSetResult; @@ -50,33 +50,33 @@ namespace winrt::Microsoft::Management::Configuration::implementation bool GenerateTelemetryEvents(); void GenerateTelemetryEvents(bool value); - event_token ConfigurationChange(const Windows::Foundation::TypedEventHandler<ConfigurationSet, ConfigurationChangeData>& handler); + event_token ConfigurationChange(const Windows::Foundation::TypedEventHandler<Configuration::ConfigurationSet, Configuration::ConfigurationChangeData>& handler); void ConfigurationChange(const event_token& token) noexcept; - Windows::Foundation::Collections::IVector<ConfigurationSet> GetConfigurationHistory(); - Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<ConfigurationSet>> GetConfigurationHistoryAsync(); + Windows::Foundation::Collections::IVector<Configuration::ConfigurationSet> GetConfigurationHistory(); + Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<Configuration::ConfigurationSet>> GetConfigurationHistoryAsync(); Configuration::OpenConfigurationSetResult OpenConfigurationSet(const Windows::Storage::Streams::IInputStream& stream); Windows::Foundation::IAsyncOperation<Configuration::OpenConfigurationSetResult> OpenConfigurationSetAsync(const Windows::Storage::Streams::IInputStream& stream); Windows::Foundation::Collections::IVector<ConfigurationConflict> CheckForConflicts( - const Windows::Foundation::Collections::IVectorView<ConfigurationSet>& configurationSets, + const Windows::Foundation::Collections::IVectorView<Configuration::ConfigurationSet>& configurationSets, bool includeConfigurationHistory); Windows::Foundation::IAsyncOperation<Windows::Foundation::Collections::IVector<ConfigurationConflict>> CheckForConflictsAsync( - const Windows::Foundation::Collections::IVectorView<ConfigurationSet>& configurationSets, + const Windows::Foundation::Collections::IVectorView<Configuration::ConfigurationSet>& configurationSets, bool includeConfigurationHistory); - GetConfigurationSetDetailsResult GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); - Windows::Foundation::IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); + GetConfigurationSetDetailsResult GetSetDetails(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); + Windows::Foundation::IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); GetConfigurationUnitDetailsResult GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); Windows::Foundation::IAsyncOperation<GetConfigurationUnitDetailsResult> GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); - ApplyConfigurationSetResult ApplySet(const ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); - Windows::Foundation::IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> ApplySetAsync(const ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); + ApplyConfigurationSetResult ApplySet(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); + Windows::Foundation::IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> ApplySetAsync(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); - TestConfigurationSetResult TestSet(const ConfigurationSet& configurationSet); - Windows::Foundation::IAsyncOperationWithProgress<TestConfigurationSetResult, TestConfigurationUnitResult> TestSetAsync(const ConfigurationSet& configurationSet); + TestConfigurationSetResult TestSet(const Configuration::ConfigurationSet& configurationSet); + Windows::Foundation::IAsyncOperationWithProgress<TestConfigurationSetResult, TestConfigurationUnitResult> TestSetAsync(const Configuration::ConfigurationSet& configurationSet); GetConfigurationUnitSettingsResult GetUnitSettings(const ConfigurationUnit& unit); Windows::Foundation::IAsyncOperation<GetConfigurationUnitSettingsResult> GetUnitSettingsAsync(const ConfigurationUnit& unit); @@ -98,39 +98,39 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Temporary entry point to enable experimental schema support. void SetSupportsSchema03(bool value); - // Removes the history for the given set. - void RemoveHistory(const ConfigurationSet& configurationSet); + // Indicate a configuration change occurred. + void ConfigurationChange(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data); private: - Windows::Foundation::Collections::IVector<ConfigurationSet> GetConfigurationHistoryImpl(AppInstaller::WinRT::AsyncCancellation cancellation = {}); + Windows::Foundation::Collections::IVector<Configuration::ConfigurationSet> GetConfigurationHistoryImpl(AppInstaller::WinRT::AsyncCancellation cancellation = {}); GetConfigurationSetDetailsResult GetSetDetailsImpl( - const ConfigurationSet& configurationSet, + const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags, AppInstaller::WinRT::AsyncProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> progress = {}); GetConfigurationUnitDetailsResult GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); ApplyConfigurationSetResult ApplySetImpl( - const ConfigurationSet& configurationSet, + const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags, AppInstaller::WinRT::AsyncProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> progress = {}); TestConfigurationSetResult TestSetImpl( - const ConfigurationSet& configurationSet, + const Configuration::ConfigurationSet& configurationSet, AppInstaller::WinRT::AsyncProgress<TestConfigurationSetResult, TestConfigurationUnitResult> progress = {}); GetConfigurationUnitSettingsResult GetUnitSettingsImpl(const ConfigurationUnit& unit, AppInstaller::WinRT::AsyncCancellation cancellation = {}); GetAllConfigurationUnitSettingsResult GetAllUnitSettingsImpl(const ConfigurationUnit& unit, AppInstaller::WinRT::AsyncCancellation cancellation = {}); - IConfigurationGroupProcessor GetSetGroupProcessor(const ConfigurationSet& configurationSet); + IConfigurationGroupProcessor GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet); void SendDiagnosticsImpl(const IDiagnosticInformation& information); IConfigurationSetProcessorFactory m_factory = nullptr; event<Windows::Foundation::EventHandler<IDiagnosticInformation>> m_diagnostics; - event<Windows::Foundation::TypedEventHandler<ConfigurationSet, ConfigurationChangeData>> m_configurationChange; + event<Windows::Foundation::TypedEventHandler<Configuration::ConfigurationSet, Configuration::ConfigurationChangeData>> m_configurationChange; ConfigThreadGlobals m_threadGlobals; IConfigurationSetProcessorFactory::Diagnostics_revoker m_factoryDiagnosticsEventRevoker; DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational; @@ -139,6 +139,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation bool m_isHandlingDiagnostics = false; // Temporary value to enable experimental schema support. bool m_supportSchema03 = true; + std::shared_ptr<ConfigurationStatus::ChangeRegistration> m_changeRegistration; #endif }; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSequencer.cpp b/src/Microsoft.Management.Configuration/ConfigurationSequencer.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "ConfigurationSequencer.h" +#include "ConfigurationStatus.h" #include <AppInstallerStrings.h> using namespace std::chrono_literals; @@ -16,6 +17,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation try { m_database.RemoveQueueItem(m_queueItemObjectName); + + auto status = ConfigurationStatus::Instance(); + status->UpdateSetState(m_setInstanceIdentifier, false); } CATCH_LOG(); } @@ -24,6 +28,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation // It then performs the equivalent of `Wait` with a timeout of 0. bool ConfigurationSequencer::Enqueue(const Configuration::ConfigurationSet& configurationSet) { + m_setInstanceIdentifier = configurationSet.InstanceIdentifier(); + // Create an arbitrarily named object std::wstring objectName = L"WinGetConfigQueue_" + AppInstaller::Utility::CreateNewGuidNameWString(); m_queueItemObjectName = AppInstaller::Utility::ConvertToUTF8(objectName); @@ -31,6 +37,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_database.AddQueueItem(configurationSet, m_queueItemObjectName); + auto statusInstance = ConfigurationStatus::Instance(); + statusInstance->UpdateSetState(m_setInstanceIdentifier, true); + // Create shared mutex constexpr PCWSTR applyMutexName = L"WinGetConfigQueueApplyMutex"; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSequencer.h b/src/Microsoft.Management.Configuration/ConfigurationSequencer.h @@ -39,6 +39,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation using QueueObjectType = wil::unique_event; ConfigurationDatabase& m_database; + guid m_setInstanceIdentifier{}; std::string m_queueItemObjectName; QueueObjectType m_queueItemObject; wil::unique_mutex m_applyMutex; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.cpp b/src/Microsoft.Management.Configuration/ConfigurationSet.cpp @@ -18,7 +18,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } ConfigurationSet::ConfigurationSet(const guid& instanceIdentifier) : - m_instanceIdentifier(instanceIdentifier), m_fromHistory(true) + m_instanceIdentifier(instanceIdentifier) { } @@ -32,11 +32,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_parameters = winrt::multi_threaded_vector<Configuration::ConfigurationParameter>(std::move(value)); } - bool ConfigurationSet::IsFromHistory() const - { - return m_fromHistory; - } - hstring ConfigurationSet::Name() { return m_name; @@ -74,27 +69,26 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigurationSetState ConfigurationSet::State() { - return ConfigurationSetState::Unknown; + auto status = ConfigurationStatus::Instance(); + return status->GetSetState(m_instanceIdentifier); } clock::time_point ConfigurationSet::FirstApply() { - return m_firstApply; - } - - void ConfigurationSet::FirstApply(clock::time_point value) - { - m_firstApply = value; + auto status = ConfigurationStatus::Instance(); + return status->GetSetFirstApply(m_instanceIdentifier); } clock::time_point ConfigurationSet::ApplyBegun() { - return clock::time_point{}; + auto status = ConfigurationStatus::Instance(); + return status->GetSetApplyBegun(m_instanceIdentifier); } clock::time_point ConfigurationSet::ApplyEnded() { - return clock::time_point{}; + auto status = ConfigurationStatus::Instance(); + return status->GetSetApplyEnded(m_instanceIdentifier); } Windows::Foundation::Collections::IVector<Configuration::ConfigurationUnit> ConfigurationSet::Units() @@ -120,14 +114,48 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_schemaVersion = value; } - event_token ConfigurationSet::ConfigurationSetChange(const Windows::Foundation::TypedEventHandler<WinRT_Self, ConfigurationSetChangeData>& handler) + void ConfigurationSet::ConfigurationSetChange(com_ptr<ConfigurationSetChangeData>& data, const std::optional<guid>& unitInstanceIdentifier) try + { + if (unitInstanceIdentifier) + { + Windows::Foundation::Collections::IVector<ConfigurationUnit> comUnits = m_units; + + std::vector<ConfigurationUnit> units{ comUnits.Size() }; + units.resize(comUnits.GetMany(0, units)); + + for (const ConfigurationUnit& unit : units) + { + if (unit.InstanceIdentifier() == unitInstanceIdentifier.value()) + { + data->Unit(unit); + break; + } + } + } + + m_configurationSetChange(*get_strong(), *data); + } + CATCH_LOG(); + + event_token ConfigurationSet::ConfigurationSetChange(const Windows::Foundation::TypedEventHandler<WinRT_Self, Configuration::ConfigurationSetChangeData>& handler) { + if (!m_configurationSetChange) + { + auto status = ConfigurationStatus::Instance(); + std::atomic_store(&m_setChangeRegistration, status->RegisterForSetChange(*this)); + } + return m_configurationSetChange.add(handler); } void ConfigurationSet::ConfigurationSetChange(const event_token& token) noexcept { m_configurationSetChange.remove(token); + + if (!m_configurationSetChange) + { + std::atomic_store(&m_setChangeRegistration, {}); + } } void ConfigurationSet::Serialize(const Windows::Storage::Streams::IOutputStream& stream) diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.h b/src/Microsoft.Management.Configuration/ConfigurationSet.h @@ -2,6 +2,8 @@ // Licensed under the MIT License. #pragma once #include "ConfigurationSet.g.h" +#include "ConfigurationSetChangeData.h" +#include "ConfigurationStatus.h" #include <winget/ILifetimeWatcher.h> #include <winget/ModuleCountBase.h> #include <winrt/Windows.Foundation.h> @@ -20,11 +22,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) ConfigurationSet(const guid& instanceIdentifier); - void FirstApply(clock::time_point value); void Units(std::vector<Configuration::ConfigurationUnit>&& units); void Parameters(std::vector<Configuration::ConfigurationParameter>&& value); - - bool IsFromHistory() const; + void ConfigurationSetChange(com_ptr<ConfigurationSetChangeData>& data, const std::optional<guid>& unitInstanceIdentifier); #endif hstring Name(); @@ -48,7 +48,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation hstring SchemaVersion(); void SchemaVersion(const hstring& value); - event_token ConfigurationSetChange(const Windows::Foundation::TypedEventHandler<WinRT_Self, ConfigurationSetChangeData>& handler); + event_token ConfigurationSetChange(const Windows::Foundation::TypedEventHandler<WinRT_Self, Configuration::ConfigurationSetChangeData>& handler); void ConfigurationSetChange(const event_token& token) noexcept; void Serialize(const Windows::Storage::Streams::IOutputStream& stream); @@ -81,13 +81,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation clock::time_point m_firstApply{}; Windows::Foundation::Collections::IVector<ConfigurationUnit> m_units{ winrt::multi_threaded_vector<ConfigurationUnit>() }; hstring m_schemaVersion; - winrt::event<Windows::Foundation::TypedEventHandler<WinRT_Self, ConfigurationSetChangeData>> m_configurationSetChange; + winrt::event<Windows::Foundation::TypedEventHandler<WinRT_Self, Configuration::ConfigurationSetChangeData>> m_configurationSetChange; Windows::Foundation::Collections::ValueSet m_metadata; Windows::Foundation::Collections::IVector<ConfigurationParameter> m_parameters{ winrt::multi_threaded_vector<ConfigurationParameter>() }; Windows::Foundation::Collections::ValueSet m_variables; Windows::Foundation::Uri m_schemaUri = nullptr; std::string m_inputHash; - bool m_fromHistory = false; + std::shared_ptr<ConfigurationStatus::SetChangeRegistration> m_setChangeRegistration; #endif }; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp @@ -8,14 +8,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation { Configuration::ConfigurationSetChangeData ConfigurationSetChangeData::Create(ConfigurationSetState state) { - auto result = make_self<wil::details::module_count_wrapper<implementation::ConfigurationSetChangeData>>(); + auto result = make_self<implementation::ConfigurationSetChangeData>(); result->Initialize(state); return *result; } Configuration::ConfigurationSetChangeData ConfigurationSetChangeData::Create(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit) { - auto result = make_self<wil::details::module_count_wrapper<implementation::ConfigurationSetChangeData>>(); + auto result = make_self<implementation::ConfigurationSetChangeData>(); result->Initialize(state, resultInformation, unit); return *result; } @@ -68,4 +68,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation { return m_unit; } + + void ConfigurationSetChangeData::Unit(const ConfigurationUnit& unit) + { + m_unit = unit; + } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h @@ -3,10 +3,11 @@ #pragma once #include "ConfigurationSetChangeData.g.h" #include "ConfigurationUnitResultInformation.h" +#include <winget/ModuleCountBase.h> namespace winrt::Microsoft::Management::Configuration::implementation { - struct ConfigurationSetChangeData : ConfigurationSetChangeDataT<ConfigurationSetChangeData> + struct ConfigurationSetChangeData : ConfigurationSetChangeDataT<ConfigurationSetChangeData>, AppInstaller::WinRT::ModuleCountBase { using ConfigurationUnit = Configuration::ConfigurationUnit; @@ -19,6 +20,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation void Initialize(ConfigurationSetState state); void Initialize(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit); void Initialize(const IApplyGroupMemberSettingsResult& unitResult); + + void Unit(const ConfigurationUnit& unit); #endif ConfigurationSetChangeEventType Change(); diff --git a/src/Microsoft.Management.Configuration/ConfigurationStatus.cpp b/src/Microsoft.Management.Configuration/ConfigurationStatus.cpp @@ -0,0 +1,410 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationStatus.h" +#include "ConfigurationChangeData.h" +#include "ConfigurationProcessor.h" +#include "ConfigurationSet.h" +#include "ConfigurationUnitResultInformation.h" +#include <AppInstallerStrings.h> +#include <AppInstallerLanguageUtilities.h> + + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace details + { + // Implements the consuming side of the status signaling. + struct ChangeListener + { + struct SetStatusItem + { + ConfigurationDatabase::StatusItem Status; + com_ptr<implementation::ConfigurationSet> Set; + }; + + ChangeListener(ConfigurationStatus& status) : m_status(status) + { + ConfigurationDatabase::StatusBaseline baseline = m_status.Database().GetStatusBaseline(); + m_changeIdentifier = baseline.ChangeIdentifier; + + for (const auto& item : baseline.SetStatus) + { + m_lastSetStatus.emplace(item.SetInstanceIdentifier, SetStatusItem{ item }); + } + + std::wstring objectName = L"WinGetConfigListener_" + AppInstaller::Utility::CreateNewGuidNameWString(); + m_listenerEventName = AppInstaller::Utility::ConvertToUTF8(objectName); + m_listenerEvent.create(wil::EventOptions::None, objectName.c_str()); + + m_status.Database().AddListener(m_listenerEventName); + + m_threadPoolWait.reset(CreateThreadpoolWait(StaticWaitCallback, this, nullptr)); + THROW_LAST_ERROR_IF(!m_threadPoolWait); + + SetThreadpoolWait(m_threadPoolWait.get(), m_listenerEvent.get(), NULL); + } + + ~ChangeListener() + { + try + { + m_status.Database().RemoveListener(m_listenerEventName); + } + CATCH_LOG(); + } + + private: + static void NTAPI StaticWaitCallback(PTP_CALLBACK_INSTANCE, void* context, TP_WAIT*, TP_WAIT_RESULT) + { + reinterpret_cast<ChangeListener*>(context)->WaitCallback(); + } + + void WaitCallback() try + { + std::vector<ConfigurationDatabase::StatusItem> changes = m_status.Database().GetStatusSince(m_changeIdentifier); + + // Convert status items to relevant change information + for (const auto& change : changes) + { + if (change.UnitInstanceIdentifier) + { + if (m_status.HasSetChangeRegistration(change.SetInstanceIdentifier)) + { + // A unit status change + ConfigurationUnitState state = AppInstaller::ToEnum<ConfigurationUnitState>(change.State); + + decltype(make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>()) resultInformation; + + if (change.ResultCode) + { + resultInformation = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>(); + resultInformation->ResultCode(change.ResultCode.value()); + resultInformation->Description(hstring{ AppInstaller::Utility::ConvertToUTF16(change.ResultDescription) }); + resultInformation->Details(hstring{ AppInstaller::Utility::ConvertToUTF16(change.ResultDetails) }); + resultInformation->ResultSource(change.ResultSource); + } + + auto changeData = make_self<implementation::ConfigurationSetChangeData>(); + changeData->Initialize(state, *resultInformation, nullptr); + + m_status.SetChangeDetected(change.SetInstanceIdentifier, changeData, change.UnitInstanceIdentifier); + } + } + else + { + // A set status change + ConfigurationSetState state = AppInstaller::ToEnum<ConfigurationSetState>(change.State); + ConfigurationChangeEventType changeType = ConfigurationChangeEventType::Unknown; + + SetStatusItem* setStatusItem = nullptr; + auto itr = m_lastSetStatus.find(change.SetInstanceIdentifier); + if (itr != m_lastSetStatus.end()) + { + setStatusItem = &itr->second; + } + + if (!setStatusItem) + { + changeType = ConfigurationChangeEventType::SetAdded; + + std::tie(itr, std::ignore) = m_lastSetStatus.emplace(change.SetInstanceIdentifier, SetStatusItem{ change }); + setStatusItem = &itr->second; + } + else + { + changeType = (change.InQueue ? ConfigurationChangeEventType::SetStateChanged : ConfigurationChangeEventType::SetRemoved); + } + + if (m_status.HasChangeRegistrations()) + { + if (!setStatusItem->Set) + { + setStatusItem->Set = m_status.Database().GetSet(change.SetInstanceIdentifier); + } + + auto changeData = make_self<wil::details::module_count_wrapper<implementation::ConfigurationChangeData>>(); + changeData->Initialize(changeType, change.SetInstanceIdentifier, state); + + m_status.ChangeDetected(*setStatusItem->Set, *changeData); + } + + auto setChangeData = make_self<implementation::ConfigurationSetChangeData>(); + setChangeData->Initialize(state); + + m_status.SetChangeDetected(change.SetInstanceIdentifier, setChangeData, std::nullopt); + } + + m_changeIdentifier = change.ChangeIdentifier; + } + + SetThreadpoolWait(m_threadPoolWait.get(), m_listenerEvent.get(), NULL); + } + CATCH_LOG_MSG("ChangeListener::WaitCallback exception"); + + ConfigurationStatus& m_status; + int64_t m_changeIdentifier; + std::map<winrt::guid, SetStatusItem> m_lastSetStatus; + wil::unique_event m_listenerEvent; + std::string m_listenerEventName; + + // Keep last to destroy first + wil::unique_threadpool_wait m_threadPoolWait; + }; + } + + ConfigurationStatus::ConfigurationStatus(private_construction) {} + + ConfigurationStatus::~ConfigurationStatus() = default; + + std::shared_ptr<ConfigurationStatus> ConfigurationStatus::Instance() + { + static std::shared_ptr<ConfigurationStatus> s_instance; + + std::shared_ptr<ConfigurationStatus> result = std::atomic_load(&s_instance); + if (!result) + { + result = std::make_shared<ConfigurationStatus>(private_construction{}); + std::shared_ptr<ConfigurationStatus> empty; + + if (!std::atomic_compare_exchange_strong(&s_instance, &empty, result)) + { + result = empty; + } + } + + return result; + } + + ConfigurationSetState ConfigurationStatus::GetSetState(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return m_database.GetSetState(instanceIdentifier); + } + + clock::time_point ConfigurationStatus::GetSetFirstApply(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return clock::from_sys(m_database.GetSetFirstApply(instanceIdentifier)); + } + + clock::time_point ConfigurationStatus::GetSetApplyBegun(const winrt::guid& instanceIdentifier) + { + using system_clock = std::chrono::system_clock; + + m_database.EnsureOpened(false); + system_clock::time_point result = m_database.GetSetApplyBegun(instanceIdentifier); + return (result == system_clock::time_point{} ? clock::time_point{} : clock::from_sys(result)); + } + + clock::time_point ConfigurationStatus::GetSetApplyEnded(const winrt::guid& instanceIdentifier) + { + using system_clock = std::chrono::system_clock; + + m_database.EnsureOpened(false); + system_clock::time_point result = m_database.GetSetApplyEnded(instanceIdentifier); + return (result == system_clock::time_point{} ? clock::time_point{} : clock::from_sys(result)); + } + + ConfigurationUnitState ConfigurationStatus::GetUnitState(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return m_database.GetUnitState(instanceIdentifier); + } + + IConfigurationUnitResultInformation ConfigurationStatus::GetUnitResultInformation(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return m_database.GetUnitResultInformation(instanceIdentifier); + } + + void ConfigurationStatus::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + m_database.EnsureOpened(); + m_database.UpdateSetState(setInstanceIdentifier, state); + SignalChangeListeners(); + } + + void ConfigurationStatus::UpdateSetState(const guid& setInstanceIdentifier, bool inQueue) + { + m_database.EnsureOpened(); + m_database.UpdateSetInQueue(setInstanceIdentifier, inQueue); + SignalChangeListeners(); + } + + void ConfigurationStatus::UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr<implementation::ConfigurationSetChangeData>& changeData) + { + m_database.EnsureOpened(); + m_database.UpdateUnitState(setInstanceIdentifier, changeData); + SignalChangeListeners(); + } + + ConfigurationStatus::SetChangeRegistration::SetChangeRegistration(const winrt::guid& instanceIdentifier, ConfigurationSet* configurationSet) : + m_status(Instance()), m_instanceIdentifier(instanceIdentifier), m_configurationSet(configurationSet) {} + + ConfigurationStatus::SetChangeRegistration::~SetChangeRegistration() + { + m_status->RemoveSetChangeRegistration(m_instanceIdentifier, m_configurationSet); + } + + std::shared_ptr<ConfigurationStatus::SetChangeRegistration> ConfigurationStatus::RegisterForSetChange(ConfigurationSet& set) + { + m_database.EnsureOpened(); + + winrt::guid instanceIdentifier = set.InstanceIdentifier(); + + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + m_setChangeRegistrations.emplace(instanceIdentifier, &set); + EnableChangeListeningIfNeeded(); + } + + return std::make_shared<SetChangeRegistration>(instanceIdentifier, &set); + } + + void ConfigurationStatus::RemoveSetChangeRegistration(const winrt::guid& instanceIdentifier, ConfigurationSet* configurationSet) noexcept + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + + auto [begin, end] = m_setChangeRegistrations.equal_range(instanceIdentifier); + + for (; begin != end; ++begin) + { + if (begin->second == configurationSet) + { + m_setChangeRegistrations.erase(begin); + break; + } + } + + DisableChangeListeningIfNeeded(); + } + + ConfigurationStatus::ChangeRegistration::ChangeRegistration(const winrt::guid& instanceIdentifier) : + m_status(Instance()), m_instanceIdentifier(instanceIdentifier) {} + + ConfigurationStatus::ChangeRegistration::~ChangeRegistration() + { + m_status->RemoveChangeRegistration(m_instanceIdentifier); + } + + std::shared_ptr<ConfigurationStatus::ChangeRegistration> ConfigurationStatus::RegisterForChange(ConfigurationProcessor& processor) + { + m_database.EnsureOpened(); + + GUID instanceIdentifier; + std::ignore = CoCreateGuid(&instanceIdentifier); + + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + m_changeRegistrations.emplace_back(instanceIdentifier, &processor); + EnableChangeListeningIfNeeded(); + } + + return std::make_shared<ChangeRegistration>(instanceIdentifier); + } + + void ConfigurationStatus::RemoveChangeRegistration(const winrt::guid& instanceIdentifier) noexcept + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + + for (auto itr = m_changeRegistrations.begin(); itr != m_changeRegistrations.end(); ++itr) + { + if (itr->first == instanceIdentifier) + { + m_changeRegistrations.erase(itr); + DisableChangeListeningIfNeeded(); + return; + } + } + } + + void ConfigurationStatus::EnableChangeListeningIfNeeded() + { + if (!m_changeListener) + { + m_changeListener = std::make_unique<details::ChangeListener>(*this); + } + } + + void ConfigurationStatus::DisableChangeListeningIfNeeded() + { + if (m_changeListener && m_setChangeRegistrations.empty() && m_changeRegistrations.empty()) + { + m_changeListener.reset(); + } + } + + void ConfigurationStatus::SignalChangeListeners() + { + std::vector<ConfigurationDatabase::StatusChangeListener> changeListeners = m_database.GetChangeListeners(); + + for (const auto& listener : changeListeners) + { + std::wstring objectName = AppInstaller::Utility::ConvertToUTF16(listener.ObjectName); + wil::unique_event listenerEvent; + if (listenerEvent.try_open(objectName.c_str(), EVENT_MODIFY_STATE)) + { + listenerEvent.SetEvent(); + } + else + { + m_database.RemoveListener(listener.ObjectName); + } + } + } + + ConfigurationDatabase& ConfigurationStatus::Database() + { + return m_database; + } + + bool ConfigurationStatus::HasSetChangeRegistration(const guid& setInstanceIdentifier) + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + auto [begin, end] = m_setChangeRegistrations.equal_range(setInstanceIdentifier); + return begin != end; + } + + bool ConfigurationStatus::HasChangeRegistrations() + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + return !m_changeRegistrations.empty(); + } + + void ConfigurationStatus::SetChangeDetected(const winrt::guid& setInstanceIdentifier, com_ptr<ConfigurationSetChangeData>& data, const std::optional<GUID>& unitInstanceIdentifier) + { + std::vector<ConfigurationSet*> setChangeRegistrations; + + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + + auto [begin, end] = m_setChangeRegistrations.equal_range(setInstanceIdentifier); + + for (; begin != end; ++begin) + { + setChangeRegistrations.emplace_back(begin->second); + } + } + + for (ConfigurationSet* set : setChangeRegistrations) + { + set->ConfigurationSetChange(data, unitInstanceIdentifier); + } + } + + void ConfigurationStatus::ChangeDetected(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data) + { + std::vector<std::pair<winrt::guid, ConfigurationProcessor*>> changeRegistrations; + + { + std::lock_guard<std::mutex> lock{ m_changeRegistrationsMutex }; + changeRegistrations = m_changeRegistrations; + } + + for (const auto& registration : changeRegistrations) + { + registration.second->ConfigurationChange(set, data); + } + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationStatus.h b/src/Microsoft.Management.Configuration/ConfigurationStatus.h @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/ConfigurationDatabase.h" +#include "ConfigurationSetChangeData.h" +#include <winrt/Microsoft.Management.Configuration.h> +#include <map> +#include <memory> +#include <mutex> +#include <optional> +#include <vector> + + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Forward declarations + struct ConfigurationProcessor; + struct ConfigurationSet; + struct ConfigurationSetChangeData; + + namespace details + { + struct ChangeListener; + } + + // Provides access to overall configuration status information. + struct ConfigurationStatus + { + private: + struct private_construction {}; + + public: + friend details::ChangeListener; + + ConfigurationStatus(private_construction); + + ConfigurationStatus(const ConfigurationStatus&) = delete; + ConfigurationStatus& operator=(const ConfigurationStatus&) = delete; + + ConfigurationStatus(ConfigurationStatus&&) = delete; + ConfigurationStatus& operator=(ConfigurationStatus&&) = delete; + + ~ConfigurationStatus(); + + // Gets the singleton instance. + static std::shared_ptr<ConfigurationStatus> Instance(); + + // Get various set state information + ConfigurationSetState GetSetState(const guid& instanceIdentifier); + clock::time_point GetSetFirstApply(const guid& instanceIdentifier); + clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); + clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + IConfigurationUnitResultInformation GetUnitResultInformation(const guid& instanceIdentifier); + + // Record state changes + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + void UpdateSetState(const guid& setInstanceIdentifier, bool inQueue); + void UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr<implementation::ConfigurationSetChangeData>& changeData); + + // Keeps data for a set change listener. + struct SetChangeRegistration + { + SetChangeRegistration(const guid& instanceIdentifier, ConfigurationSet* configurationSet); + + SetChangeRegistration(const SetChangeRegistration&) = delete; + SetChangeRegistration& operator=(const SetChangeRegistration&) = delete; + + SetChangeRegistration(SetChangeRegistration&&) = delete; + SetChangeRegistration& operator=(SetChangeRegistration&&) = delete; + + ~SetChangeRegistration(); + + private: + std::shared_ptr<ConfigurationStatus> m_status; + guid m_instanceIdentifier; + ConfigurationSet* m_configurationSet; + }; + + std::shared_ptr<SetChangeRegistration> RegisterForSetChange(ConfigurationSet& set); + void RemoveSetChangeRegistration(const guid& instanceIdentifier, ConfigurationSet* configurationSet) noexcept; + + // Keeps data for a change listener. + struct ChangeRegistration + { + ChangeRegistration(const guid& instanceIdentifier); + + ChangeRegistration(const ChangeRegistration&) = delete; + ChangeRegistration& operator=(const ChangeRegistration&) = delete; + + ChangeRegistration(ChangeRegistration&&) = delete; + ChangeRegistration& operator=(ChangeRegistration&&) = delete; + + ~ChangeRegistration(); + + private: + std::shared_ptr<ConfigurationStatus> m_status; + guid m_instanceIdentifier; + }; + + std::shared_ptr<ChangeRegistration> RegisterForChange(ConfigurationProcessor& processor); + void RemoveChangeRegistration(const guid& instanceIdentifier) noexcept; + + private: + void EnableChangeListeningIfNeeded(); + void DisableChangeListeningIfNeeded(); + + void SignalChangeListeners(); + + ConfigurationDatabase& Database(); + + bool HasSetChangeRegistration(const guid& setInstanceIdentifier); + bool HasChangeRegistrations(); + + void SetChangeDetected(const guid& setInstanceIdentifier, com_ptr<ConfigurationSetChangeData>& data, const std::optional<GUID>& unitInstanceIdentifier); + void ChangeDetected(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data); + + ConfigurationDatabase m_database; + + std::mutex m_changeRegistrationsMutex; + std::multimap<guid, ConfigurationSet*> m_setChangeRegistrations; + std::vector<std::pair<guid, ConfigurationProcessor*>> m_changeRegistrations; + + // Keep this last to ensure it is destroyed first + std::unique_ptr<details::ChangeListener> m_changeListener; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp @@ -4,6 +4,7 @@ #include "ConfigurationUnit.h" #include "ConfigurationUnit.g.cpp" #include "ConfigurationSetParser.h" +#include "ConfigurationStatus.h" namespace winrt::Microsoft::Management::Configuration::implementation { @@ -137,12 +138,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigurationUnitState ConfigurationUnit::State() { - return ConfigurationUnitState::Unknown; + auto status = ConfigurationStatus::Instance(); + return status->GetUnitState(m_instanceIdentifier); } IConfigurationUnitResultInformation ConfigurationUnit::ResultInformation() { - return nullptr; + auto status = ConfigurationStatus::Instance(); + return status->GetUnitResultInformation(m_instanceIdentifier); } bool ConfigurationUnit::IsActive() diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp @@ -56,6 +56,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation { m_resultCode = resultCode; m_resultSource = resultSource; + } + + void ConfigurationUnitResultInformation::Initialize(hresult resultCode, std::wstring_view description, std::wstring_view details, ConfigurationUnitResultSource resultSource) + { + m_resultCode = resultCode; + m_description = description; + m_details = details; + m_resultSource = resultSource; } hresult ConfigurationUnitResultInformation::ResultCode() const diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.h b/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.h @@ -14,6 +14,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void Initialize(hresult resultCode, std::wstring_view description); void Initialize(hresult resultCode, hstring description); void Initialize(hresult resultCode, ConfigurationUnitResultSource resultSource); + void Initialize(hresult resultCode, std::wstring_view description, std::wstring_view details, ConfigurationUnitResultSource resultSource); #endif hresult ResultCode() const; diff --git a/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.cpp b/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.cpp @@ -3,10 +3,13 @@ #include "pch.h" #include "Database/ConfigurationDatabase.h" #include "Database/Schema/IConfigurationDatabase.h" +#include "ConfigurationUnitResultInformation.h" +#include <AppInstallerStrings.h> #include <winget/Filesystem.h> #include "Filesystem.h" using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; namespace winrt::Microsoft::Management::Configuration::implementation { @@ -22,6 +25,30 @@ namespace winrt::Microsoft::Management::Configuration::implementation constexpr std::string_view s_Database_FileName = "config.db"sv; #define s_Database_MutexName L"WindowsPackageManager_Configuration_DatabaseMutex" + + std::vector<ConfigurationDatabase::StatusItem> ConvertStatusItems(const std::vector<IConfigurationDatabase::StatusItemTuple>& input) + { + std::vector<ConfigurationDatabase::StatusItem> result; + + for (const auto& item : input) + { + ConfigurationDatabase::StatusItem statusItem{}; + std::tie( + statusItem.ChangeIdentifier, + statusItem.ChangeTime, + statusItem.SetInstanceIdentifier, + statusItem.InQueue, + statusItem.UnitInstanceIdentifier, + statusItem.State, + statusItem.ResultCode, + statusItem.ResultDescription, + statusItem.ResultDetails, + statusItem.ResultSource) = item; + result.emplace_back(std::move(statusItem)); + } + + return result; + } } ConfigurationDatabase::ConfigurationDatabase() = default; @@ -38,7 +65,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation try { #endif - if (!m_database) + if (!std::atomic_load(&m_database)) { std::filesystem::path databaseDirectory = AppInstaller::Filesystem::GetPathTo(PathName::LocalState) / s_Database_DirectoryName; std::filesystem::path databaseFile = databaseDirectory / s_Database_FileName; @@ -57,16 +84,27 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::filesystem::create_directories(databaseDirectory); - m_connection = std::make_shared<SQLiteDynamicStorage>(databaseFile, IConfigurationDatabase::GetLatestVersion()); - m_database = IConfigurationDatabase::CreateFor(m_connection); - m_database->InitializeDatabase(); + auto connection = std::make_shared<SQLiteDynamicStorage>(databaseFile, IConfigurationDatabase::GetLatestVersion()); + auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) }; + database->InitializeDatabase(); + + std::atomic_store(&m_connection, connection); + std::atomic_store(&m_database, database); } } - if (!m_database && std::filesystem::is_regular_file(databaseFile)) + if (!std::atomic_load(&m_connection)) + { + std::shared_ptr<SQLiteDynamicStorage> empty; + auto connection = std::make_shared<SQLiteDynamicStorage>(databaseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + std::atomic_compare_exchange_strong(&m_connection, &empty, connection); + } + + if (!std::atomic_load(&m_database)) { - m_connection = std::make_shared<SQLiteDynamicStorage>(databaseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - m_database = IConfigurationDatabase::CreateFor(m_connection, true); + std::shared_ptr<IConfigurationDatabase> empty; + auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(std::atomic_load(&m_connection), true) }; + std::atomic_compare_exchange_strong(&m_database, &empty, database); } } #ifdef AICLI_DISABLE_TEST_HOOKS @@ -75,211 +113,356 @@ namespace winrt::Microsoft::Management::Configuration::implementation #endif } - std::vector<ConfigurationDatabase::ConfigurationSetPtr> ConfigurationDatabase::GetSetHistory() const + template <typename OperationT> + auto ConfigurationDatabase::ExecuteReadOperation(std::string_view operationName, OperationT&& operation, bool requireDatabase) const { + using ResultT = decltype(operation(std::declval<std::shared_ptr<IConfigurationDatabase>&>())); + ResultT result{}; + #ifdef AICLI_DISABLE_TEST_HOOKS // While under development, treat errors escaping this function as a test hook. try { #endif - if (!m_database) - { - return {}; - } + auto database = std::atomic_load(&m_database); - auto transaction = BeginTransaction("GetSetHistory"); - return m_database->GetSets(); + if (database) + { + auto transaction = BeginTransaction(operationName, false, database); + result = operation(database); + } + else if (requireDatabase) + { + THROW_HR(E_NOT_VALID_STATE); + } #ifdef AICLI_DISABLE_TEST_HOOKS } CATCH_LOG(); - - return {}; #endif + + return result; } - void ConfigurationDatabase::WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory) + template <typename OperationT> + void ConfigurationDatabase::ExecuteWriteOperation(std::string_view operationName, OperationT&& operation, bool silentlyIgnoreNoDatabase) { #ifdef AICLI_DISABLE_TEST_HOOKS // While under development, treat errors escaping this function as a test hook. try { #endif - THROW_HR_IF_NULL(E_POINTER, configurationSet); - THROW_HR_IF_NULL(E_NOT_VALID_STATE, m_database); + auto database = std::atomic_load(&m_database); - auto transaction = BeginTransaction("WriteSetHistory"); - - std::optional<rowid_t> setRowId = m_database->GetSetRowId(configurationSet.InstanceIdentifier()); - - if (!setRowId && !preferNewHistory) - { - // TODO: Use conflict detection code to check for a matching set - } - - if (setRowId) - { - m_database->UpdateSet(setRowId.value(), configurationSet); - } - else - { - m_database->AddSet(configurationSet); - } - - m_connection->SetLastWriteTime(); + if (!database) + { + THROW_HR_IF(E_NOT_VALID_STATE, !silentlyIgnoreNoDatabase); + return; + } - transaction->Commit(); + auto transaction = BeginTransaction(operationName, true, database); + operation(database); + std::atomic_load(&m_connection)->SetLastWriteTime(); + transaction->Commit(); #ifdef AICLI_DISABLE_TEST_HOOKS } CATCH_LOG(); #endif } - void ConfigurationDatabase::RemoveSetHistory(const Configuration::ConfigurationSet& configurationSet) + std::vector<ConfigurationDatabase::ConfigurationSetPtr> ConfigurationDatabase::GetSetHistory() const + { + return ExecuteReadOperation("GetSetHistory", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return database->GetSets(); + }); + } + + ConfigurationDatabase::ConfigurationSetPtr ConfigurationDatabase::GetSet(const GUID& instanceIdentifier) const + { + return ExecuteReadOperation("GetSet", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return database->GetSet(instanceIdentifier); + }); + } + + void ConfigurationDatabase::WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory) { -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif THROW_HR_IF_NULL(E_POINTER, configurationSet); - if (!m_database) - { - return; - } + ExecuteWriteOperation("WriteSetHistory", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + std::optional<rowid_t> setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier()); - auto transaction = BeginTransaction("RemoveSetHistory"); + if (!setRowId && !preferNewHistory) + { + // TODO: Use conflict detection code to check for a matching set + } - std::optional<rowid_t> setRowId = m_database->GetSetRowId(configurationSet.InstanceIdentifier()); + if (setRowId) + { + database->UpdateSet(setRowId.value(), configurationSet); + } + else + { + database->AddSet(configurationSet); + } + }); + } - if (!setRowId) - { - // TODO: Use conflict detection code to check for a matching set - } + void ConfigurationDatabase::RemoveSetHistory(const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF_NULL(E_POINTER, configurationSet); - if (setRowId) - { - m_database->RemoveSet(setRowId.value()); - m_connection->SetLastWriteTime(); - } + ExecuteWriteOperation("RemoveSetHistory", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + std::optional<rowid_t> setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier()); - transaction->Commit(); -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); -#endif + if (!setRowId) + { + // TODO: Use conflict detection code to check for a matching set + } + + if (setRowId) + { + database->RemoveSet(setRowId.value()); + std::atomic_load(&m_connection)->SetLastWriteTime(); + } + }, true); } void ConfigurationDatabase::AddQueueItem(const Configuration::ConfigurationSet& configurationSet, const std::string& objectName) { -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif THROW_HR_IF_NULL(E_POINTER, configurationSet); - THROW_HR_IF_NULL(E_NOT_VALID_STATE, m_database); - auto transaction = BeginTransaction("AddQueueItem"); + ExecuteWriteOperation("AddQueueItem", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->AddQueueItem(configurationSet.InstanceIdentifier(), objectName); + }); + } - m_database->AddQueueItem(configurationSet.InstanceIdentifier(), objectName); - m_connection->SetLastWriteTime(); + void ConfigurationDatabase::SetActiveQueueItem(const std::string& objectName) + { + ExecuteWriteOperation("SetActiveQueueItem", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->SetActiveQueueItem(objectName); + }); + } - transaction->Commit(); -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); -#endif + std::vector<ConfigurationDatabase::QueueItem> ConfigurationDatabase::GetQueueItems() const + { + return ExecuteReadOperation("GetQueueItems", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + std::vector<QueueItem> result; + auto queueItems = database->GetQueueItems(); + result.reserve(queueItems.size()); + + for (const auto& item : queueItems) + { + QueueItem resultItem; + std::tie(resultItem.SetInstanceIdentifier, resultItem.ObjectName, resultItem.QueuedAt, resultItem.ProcessId, resultItem.Active) = item; + result.emplace_back(std::move(resultItem)); + } + + return result; + }, true); } - void ConfigurationDatabase::SetActiveQueueItem(const std::string& objectName) + void ConfigurationDatabase::RemoveQueueItem(const std::string& objectName) { -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif - THROW_HR_IF_NULL(E_NOT_VALID_STATE, m_database); + ExecuteWriteOperation("RemoveQueueItem", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->RemoveQueueItem(objectName); + }); + } - auto transaction = BeginTransaction("SetActiveQueueItem"); + std::vector<ConfigurationDatabase::StatusItem> ConfigurationDatabase::GetStatusSince(int64_t changeIdentifier) const + { + return ExecuteReadOperation("GetStatusSince", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return ConvertStatusItems(database->GetStatusSince(changeIdentifier)); + }); + } - m_database->SetActiveQueueItem(objectName); - m_connection->SetLastWriteTime(); + ConfigurationDatabase::StatusBaseline ConfigurationDatabase::GetStatusBaseline() const + { + return ExecuteReadOperation("GetStatusBaseline", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + auto [changeIdentifier, setStatus] = database->GetStatusBaseline(); - transaction->Commit(); -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); -#endif + StatusBaseline result{}; + result.ChangeIdentifier = changeIdentifier; + result.SetStatus = ConvertStatusItems(setStatus); + return result; + }); } - std::vector<ConfigurationDatabase::QueueItem> ConfigurationDatabase::GetQueueItems() const + void ConfigurationDatabase::AddListener(const std::string& objectName) { -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif - THROW_HR_IF_NULL(E_NOT_VALID_STATE, m_database); + ExecuteWriteOperation("AddListener", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->AddListener(objectName); + }); + } - auto transaction = BeginTransaction("GetQueueItems"); + void ConfigurationDatabase::RemoveListener(const std::string& objectName) + { + ExecuteWriteOperation("RemoveListener", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->RemoveListener(objectName); + }); + } - std::vector<ConfigurationDatabase::QueueItem> result; - auto queueItems = m_database->GetQueueItems(); - result.reserve(queueItems.size()); + std::vector<ConfigurationDatabase::StatusChangeListener> ConfigurationDatabase::GetChangeListeners() const + { + return ExecuteReadOperation("GetChangeListeners", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + std::vector<StatusChangeListener> result; - for (const auto& item : queueItems) - { - QueueItem resultItem; - std::tie(resultItem.SetInstanceIdentifier, resultItem.ObjectName, resultItem.QueuedAt, resultItem.Active) = item; - result.emplace_back(std::move(resultItem)); - } + for (const auto& item : database->GetChangeListeners()) + { + StatusChangeListener listener{}; + std::tie(listener.ObjectName, listener.Started, listener.ProcessId) = item; + result.emplace_back(std::move(listener)); + } - return result; -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); + return result; + }); + } - return {}; -#endif + void ConfigurationDatabase::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + ExecuteWriteOperation("UpdateSetState", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->UpdateSetState(setInstanceIdentifier, state); + }); } - void ConfigurationDatabase::RemoveQueueItem(const std::string& objectName) + void ConfigurationDatabase::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) { -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif - THROW_HR_IF_NULL(E_NOT_VALID_STATE, m_database); + ExecuteWriteOperation("UpdateSetInQueue", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->UpdateSetInQueue(setInstanceIdentifier, inQueue); + }); + } - auto transaction = BeginTransaction("RemoveQueueItem"); + void ConfigurationDatabase::UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr<implementation::ConfigurationSetChangeData>& changeData) + { + ExecuteWriteOperation("UpdateUnitState", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + database->UpdateUnitState(setInstanceIdentifier, changeData); + }); + } - m_database->RemoveQueueItem(objectName); - m_connection->SetLastWriteTime(); + ConfigurationSetState ConfigurationDatabase::GetSetState(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetSetState", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return database->GetSetState(instanceIdentifier); + }); + } - transaction->Commit(); -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); -#endif + std::chrono::system_clock::time_point ConfigurationDatabase::GetSetFirstApply(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetSetFirstApply", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return database->GetSetFirstApply(instanceIdentifier); + }); } - ConfigurationDatabase::TransactionLock ConfigurationDatabase::BeginTransaction(std::string_view name) const + std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyBegun(const guid& instanceIdentifier) { - THROW_HR_IF_NULL(E_NOT_VALID_STATE, m_connection); + return ExecuteReadOperation("GetSetApplyBegun", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return database->GetSetApplyBegun(instanceIdentifier); + }); + } - TransactionLock result = m_connection->TryBeginTransaction(name); + std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyEnded(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetSetApplyEnded", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return database->GetSetApplyEnded(instanceIdentifier); + }); + } + + ConfigurationUnitState ConfigurationDatabase::GetUnitState(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetUnitState", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + return database->GetUnitState(instanceIdentifier); + }); + } + + IConfigurationUnitResultInformation ConfigurationDatabase::GetUnitResultInformation(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetUnitResultInformation", + [&](std::shared_ptr<IConfigurationDatabase>& database) + { + com_ptr<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>> result; + + auto resultInformation = database->GetUnitResultInformation(instanceIdentifier); + + if (resultInformation) + { + result = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>(); + result->Initialize( + std::get<0>(resultInformation.value()), + ConvertToUTF16(std::get<1>(resultInformation.value())), + ConvertToUTF16(std::get<2>(resultInformation.value())), + std::get<3>(resultInformation.value())); + } + + IConfigurationUnitResultInformation actualResult; + if (result) + { + actualResult = *result; + } + + return actualResult; + }); + } + + ConfigurationDatabase::TransactionLock ConfigurationDatabase::BeginTransaction(std::string_view name, bool forWrite, std::shared_ptr<IConfigurationDatabase>& database) const + { + auto connection = std::atomic_load(&m_connection); + THROW_HR_IF_NULL(E_NOT_VALID_STATE, connection); + + TransactionLock result = connection->TryBeginTransaction(name, forWrite); while (!result) { { - auto connectionLock = m_connection->LockConnection(); - m_database = IConfigurationDatabase::CreateFor(m_connection); + auto connectionLock = connection->LockConnection(); + auto newDatabase = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) }; + if (std::atomic_compare_exchange_strong(&m_database, &database, newDatabase)) + { + database = newDatabase; + } } - result = m_connection->TryBeginTransaction(name); + result = connection->TryBeginTransaction(name, forWrite); } return result; diff --git a/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.h b/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.h @@ -1,15 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "ConfigurationSet.h" +#include "ConfigurationSetChangeData.h" #include <winget/SQLiteWrapper.h> #include <winget/SQLiteDynamicStorage.h> +#include <winrt/Microsoft.Management.Configuration.h> #include <memory> +#include <optional> #include <vector> namespace winrt::Microsoft::Management::Configuration::implementation { - // Forward declaration of internal interface. + // Forward declarations + struct ConfigurationSet; struct IConfigurationDatabase; // Allows access to the configuration database. @@ -35,6 +38,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Gets all of the configuration sets from the database. std::vector<ConfigurationSetPtr> GetSetHistory() const; + // Gets the set with the given identifier. + ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) const; + // Writes the given set to the database history, attempting to merge with a matching set if one exists unless preferNewHistory is true. void WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory); @@ -53,6 +59,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation GUID SetInstanceIdentifier{}; std::string ObjectName; std::chrono::system_clock::time_point QueuedAt; + DWORD ProcessId{}; bool Active = false; }; @@ -62,13 +69,83 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Removes the queue item with the given object name. void RemoveQueueItem(const std::string& objectName); + // A status line item. + struct StatusItem + { + int64_t ChangeIdentifier; + std::chrono::system_clock::time_point ChangeTime; + GUID SetInstanceIdentifier; + bool InQueue; + std::optional<GUID> UnitInstanceIdentifier; + int32_t State; + std::optional<HRESULT> ResultCode; + std::string ResultDescription; + std::string ResultDetails; + ConfigurationUnitResultSource ResultSource; + }; + + // Gets all changed status items after the given change identifier. + std::vector<StatusItem> GetStatusSince(int64_t changeIdentifier) const; + + // The status baseline data. + struct StatusBaseline + { + int64_t ChangeIdentifier = 0; + std::vector<StatusItem> SetStatus; + }; + + // Gets the current status baseline. + StatusBaseline GetStatusBaseline() const; + + // Data about a status change listener. + struct StatusChangeListener + { + std::string ObjectName; + std::chrono::system_clock::time_point Started; + DWORD ProcessId{}; + }; + + // Adds a listener to the database. + void AddListener(const std::string& objectName); + + // Removes a listener from the database. + void RemoveListener(const std::string& objectName); + + // Gets all listeners in the database. + std::vector<StatusChangeListener> GetChangeListeners() const; + + // Updates the set state in the database. + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + + // Updates the set "in queue" state in the database. + void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); + + // Updates the unit state in the database. + void UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr<implementation::ConfigurationSetChangeData>& changeData); + + // Read various status values. + ConfigurationSetState GetSetState(const guid& instanceIdentifier); + std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier); + std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); + std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + IConfigurationUnitResultInformation GetUnitResultInformation(const guid& instanceIdentifier); + private: std::shared_ptr<AppInstaller::SQLite::SQLiteDynamicStorage> m_connection; - mutable std::unique_ptr<IConfigurationDatabase> m_database; + mutable std::shared_ptr<IConfigurationDatabase> m_database; - using TransactionLock = decltype(m_connection->TryBeginTransaction({})); + using TransactionLock = decltype(m_connection->TryBeginTransaction({}, true)); // Begins a transaction, which may require upgrading to a newer schema version. - TransactionLock BeginTransaction(std::string_view name) const; + TransactionLock BeginTransaction(std::string_view name, bool forWrite, std::shared_ptr<IConfigurationDatabase>& database) const; + + // Performs the boilerplate setup for a read, then executes the given operation. + template <typename OperationT> + auto ExecuteReadOperation(std::string_view operationName, OperationT&& operation, bool requireDatabase = false) const; + + // Performs the boilerplate setup for a write, then executes the given operation. + template <typename OperationT> + void ExecuteWriteOperation(std::string_view operationName, OperationT&& operation, bool silentlyIgnoreNoDatabase = false); }; } diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface.h b/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface.h @@ -22,6 +22,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: // Version 0.2 bool MigrateFrom(IConfigurationDatabase* current) override; + // Version 0.3 + ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) override; + protected: std::shared_ptr<AppInstaller::SQLite::SQLiteDynamicStorage> m_storage; }; diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface_0_1.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface_0_1.cpp @@ -83,4 +83,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: { return current->GetSchemaVersion() == s_InterfaceVersion; } + + Interface::ConfigurationSetPtr Interface::GetSet(const GUID& instanceIdentifier) + { + SetInfoTable setInfoTable(*m_storage); + return setInfoTable.GetSet(instanceIdentifier); + } } diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp @@ -28,10 +28,61 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: constexpr std::string_view s_SetInfoTable_Column_Metadata = "metadata"sv; constexpr std::string_view s_SetInfoTable_Column_Parameters = "parameters"sv; constexpr std::string_view s_SetInfoTable_Column_Variables = "variables"sv; + + void BuildBaseSetSelectStatement(StatementBuilder& builder) + { + builder.Select({ + RowIDName, // 0 + s_SetInfoTable_Column_InstanceIdentifier, // 1 + s_SetInfoTable_Column_Name, // 2 + s_SetInfoTable_Column_Origin, // 3 + s_SetInfoTable_Column_Path, // 4 + s_SetInfoTable_Column_SchemaVersion, // 5 + s_SetInfoTable_Column_Metadata, // 6 + s_SetInfoTable_Column_Parameters, // 7 + s_SetInfoTable_Column_Variables, // 8 + }).From(s_SetInfoTable_Table); + } + + IConfigurationDatabase::ConfigurationSetPtr GetSetFromStatement(Statement& statement, UnitInfoTable& unitInfoTable) + { + auto configurationSet = make_self<implementation::ConfigurationSet>(statement.GetColumn<GUID>(1)); + + configurationSet->Name(hstring{ ConvertToUTF16(statement.GetColumn<std::string>(2)) }); + configurationSet->Origin(hstring{ ConvertToUTF16(statement.GetColumn<std::string>(3)) }); + configurationSet->Path(hstring{ ConvertToUTF16(statement.GetColumn<std::string>(4)) }); + + std::string schemaVersion = statement.GetColumn<std::string>(5); + configurationSet->SchemaVersion(hstring{ ConvertToUTF16(schemaVersion) }); + + auto parser = ConfigurationSetParser::CreateForSchemaVersion(schemaVersion); + configurationSet->Metadata(parser->ParseValueSet(statement.GetColumn<std::string>(6))); + THROW_HR_IF(E_NOTIMPL, !statement.GetColumn<std::string>(7).empty()); + configurationSet->Variables(parser->ParseValueSet(statement.GetColumn<std::string>(8))); + + std::vector<Configuration::ConfigurationUnit> winrtUnits; + for (const auto& unit : unitInfoTable.GetAllUnitsForSet(statement.GetColumn<rowid_t>(0), schemaVersion)) + { + winrtUnits.emplace_back(*unit); + } + configurationSet->Units(std::move(winrtUnits)); + + return configurationSet; + } } SetInfoTable::SetInfoTable(Connection& connection) : m_connection(connection) {} + std::string_view SetInfoTable::TableName() + { + return s_SetInfoTable_Table; + } + + std::string_view SetInfoTable::InstanceIdentifierColumn() + { + return s_SetInfoTable_Column_InstanceIdentifier; + } + void SetInfoTable::Create() { Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Create_0_1"); @@ -151,18 +202,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: std::vector<IConfigurationDatabase::ConfigurationSetPtr> result; StatementBuilder builder; - builder.Select({ - RowIDName, // 0 - s_SetInfoTable_Column_InstanceIdentifier, // 1 - s_SetInfoTable_Column_Name, // 2 - s_SetInfoTable_Column_Origin, // 3 - s_SetInfoTable_Column_Path, // 4 - s_SetInfoTable_Column_FirstApply, // 5 - s_SetInfoTable_Column_SchemaVersion, // 6 - s_SetInfoTable_Column_Metadata, // 7 - s_SetInfoTable_Column_Parameters, // 8 - s_SetInfoTable_Column_Variables, // 9 - }).From(s_SetInfoTable_Table); + BuildBaseSetSelectStatement(builder); Statement getAllSets = builder.Prepare(m_connection); @@ -170,29 +210,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: while (getAllSets.Step()) { - auto configurationSet = make_self<implementation::ConfigurationSet>(getAllSets.GetColumn<GUID>(1)); - - configurationSet->Name(hstring{ ConvertToUTF16(getAllSets.GetColumn<std::string>(2)) }); - configurationSet->Origin(hstring{ ConvertToUTF16(getAllSets.GetColumn<std::string>(3)) }); - configurationSet->Path(hstring{ ConvertToUTF16(getAllSets.GetColumn<std::string>(4)) }); - configurationSet->FirstApply(clock::from_sys(ConvertUnixEpochToSystemClock(getAllSets.GetColumn<int64_t>(5)))); - - std::string schemaVersion = getAllSets.GetColumn<std::string>(6); - configurationSet->SchemaVersion(hstring{ ConvertToUTF16(schemaVersion) }); - - auto parser = ConfigurationSetParser::CreateForSchemaVersion(schemaVersion); - configurationSet->Metadata(parser->ParseValueSet(getAllSets.GetColumn<std::string>(7))); - THROW_HR_IF(E_NOTIMPL, !getAllSets.GetColumn<std::string>(8).empty()); - configurationSet->Variables(parser->ParseValueSet(getAllSets.GetColumn<std::string>(9))); - - std::vector<Configuration::ConfigurationUnit> winrtUnits; - for (const auto& unit : unitInfoTable.GetAllUnitsForSet(getAllSets.GetColumn<rowid_t>(0), schemaVersion)) - { - winrtUnits.emplace_back(*unit); - } - configurationSet->Units(std::move(winrtUnits)); - - result.emplace_back(std::move(configurationSet)); + result.emplace_back(GetSetFromStatement(getAllSets, unitInfoTable)); } return result; @@ -212,4 +230,33 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: return std::nullopt; } + + IConfigurationDatabase::ConfigurationSetPtr SetInfoTable::GetSet(const GUID& instanceIdentifier) + { + IConfigurationDatabase::ConfigurationSetPtr result; + + StatementBuilder builder; + BuildBaseSetSelectStatement(builder); + builder.Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement getSet = builder.Prepare(m_connection); + + if (getSet.Step()) + { + UnitInfoTable unitInfoTable(m_connection); + result = GetSetFromStatement(getSet, unitInfoTable); + } + + return result; + } + + std::chrono::system_clock::time_point SetInfoTable::GetSetFirstApply(const GUID& instanceIdentifier) + { + StatementBuilder builder; + builder.Select(s_SetInfoTable_Column_FirstApply).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement statement = builder.Prepare(m_connection); + + return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(0)) : std::chrono::system_clock::time_point{}); + } } diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.h @@ -13,6 +13,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: { SetInfoTable(AppInstaller::SQLite::Connection& connection); + static std::string_view TableName(); + static std::string_view InstanceIdentifierColumn(); + // Creates the set info table. void Create(); @@ -32,6 +35,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: // Gets the row id of the set with the given instance identifier. std::optional<AppInstaller::SQLite::rowid_t> GetSetRowId(const GUID& instanceIdentifier); + // Gets the set with the given instance identifier. + IConfigurationDatabase::ConfigurationSetPtr GetSet(const GUID& instanceIdentifier); + + // Gets a set's first apply time. + std::chrono::system_clock::time_point GetSetFirstApply(const GUID& instanceIdentifier); + private: AppInstaller::SQLite::Connection& m_connection; }; diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface.h b/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface.h @@ -19,7 +19,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: bool MigrateFrom(IConfigurationDatabase* current) override; void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) override; void SetActiveQueueItem(const std::string& objectName) override; - std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, bool>> GetQueueItems() override; + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> GetQueueItems() override; void RemoveQueueItem(const std::string& objectName) override; private: diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface_0_2.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface_0_2.cpp @@ -20,7 +20,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: void Interface::InitializeDatabase() { V0_1::Interface::InitializeDatabase(); + + Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_2"); MigrateFrom0_1(); + savepoint.Commit(); } bool Interface::MigrateFrom(IConfigurationDatabase* current) @@ -51,7 +54,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: void Interface::AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) { QueueTable queueTable(*m_storage); - queueTable.AddQueueItem(instanceIdentifier, objectName); + queueTable.AddQueueItemWithoutProcess(instanceIdentifier, objectName); } void Interface::SetActiveQueueItem(const std::string& objectName) @@ -60,10 +63,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: queueTable.SetActiveQueueItem(objectName); } - std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, bool>> Interface::GetQueueItems() + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> Interface::GetQueueItems() { QueueTable queueTable(*m_storage); - return queueTable.GetQueueItems(); + return queueTable.GetQueueItemsWithoutProcess(); } void Interface::RemoveQueueItem(const std::string& objectName) diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp @@ -19,6 +19,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: constexpr std::string_view s_QueueTable_Column_ObjectName = "object_name"sv; constexpr std::string_view s_QueueTable_Column_QueuedAt = "queued_at"sv; constexpr std::string_view s_QueueTable_Column_Active = "active"sv; + constexpr std::string_view s_QueueTable_Column_Process = "process"sv; } QueueTable::QueueTable(Connection& connection) : m_connection(connection) {} @@ -41,7 +42,19 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: savepoint.Commit(); } - void QueueTable::AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) + void QueueTable::AddProcessColumn() + { + Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_AddProcessColumn_0_3"); + + StatementBuilder builder; + builder.AlterTable(s_QueueTable_Table).Add(s_QueueTable_Column_Process, Type::Int64).NotNull().Default(0); + + builder.Execute(m_connection); + + savepoint.Commit(); + } + + void QueueTable::AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName) { StatementBuilder builder; builder.InsertInto(s_QueueTable_Table).Columns({ @@ -59,6 +72,26 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: builder.Execute(m_connection); } + void QueueTable::AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName) + { + StatementBuilder builder; + builder.InsertInto(s_QueueTable_Table).Columns({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active, + s_QueueTable_Column_Process + }).Values( + instanceIdentifier, + objectName, + GetCurrentUnixEpoch(), + false, + static_cast<int64_t>(GetCurrentProcessId()) + ); + + builder.Execute(m_connection); + } + void QueueTable::SetActiveQueueItem(const std::string& objectName) { StatementBuilder builder; @@ -67,7 +100,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: builder.Execute(m_connection); } - std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, bool>> QueueTable::GetQueueItems() + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> QueueTable::GetQueueItemsWithoutProcess() { StatementBuilder builder; builder.Select({ @@ -75,15 +108,38 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: s_QueueTable_Column_ObjectName, s_QueueTable_Column_QueuedAt, s_QueueTable_Column_Active + }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); + + Statement statement = builder.Prepare(m_connection); + + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> result; + + while (statement.Step()) + { + result.emplace_back(std::make_tuple(statement.GetColumn<GUID>(0), statement.GetColumn<std::string>(1), ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(2)), 0, statement.GetColumn<bool>(3))); + } + + return result; + } + + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> QueueTable::GetQueueItemsWithProcess() + { + StatementBuilder builder; + builder.Select({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active, + s_QueueTable_Column_Process }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); Statement statement = builder.Prepare(m_connection); - std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, bool>> result; + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> result; while (statement.Step()) { - result.emplace_back(std::make_tuple(statement.GetColumn<GUID>(0), statement.GetColumn<std::string>(1), ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(2)), statement.GetColumn<bool>(3))); + result.emplace_back(std::make_tuple(statement.GetColumn<GUID>(0), statement.GetColumn<std::string>(1), ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(2)), static_cast<DWORD>(statement.GetColumn<int64_t>(4)), statement.GetColumn<bool>(3))); } return result; diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.h @@ -14,14 +14,23 @@ namespace winrt::Microsoft::Management::Configuration::implementation::Database: // Creates the queue table. void Create(); + // Adds the process column to the table. + void AddProcessColumn(); + + // Adds a new queue item for the given configuration set and object name. + void AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName); + // Adds a new queue item for the given configuration set and object name. - void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName); + void AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName); // Sets the queue item with the given object name as active. void SetActiveQueueItem(const std::string& objectName); // Gets all queue items in queue order (item at index 0 is active/next). - std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, bool>> GetQueueItems(); + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> GetQueueItemsWithoutProcess(); + + // Gets all queue items in queue order (item at index 0 is active/next). + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> GetQueueItemsWithProcess(); // Removes the queue item with the given object name. void RemoveQueueItem(const std::string& objectName); diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.cpp @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ChangeListenerTable.h" +#include <AppInstallerDateTime.h> +#include <winget/SQLiteStatementBuilder.h> + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + namespace + { + constexpr std::string_view s_ChangeListenerTable_Table = "change_listeners"sv; + + constexpr std::string_view s_ChangeListenerTable_Column_ObjectName = "object_name"sv; + constexpr std::string_view s_ChangeListenerTable_Column_StartedAt = "started_at"sv; + constexpr std::string_view s_ChangeListenerTable_Column_Process = "process"sv; + } + + ChangeListenerTable::ChangeListenerTable(Connection& connection) : m_connection(connection) {} + + void ChangeListenerTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "ChangeListenerTable_Create_0_3"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_ChangeListenerTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_ChangeListenerTable_Column_ObjectName, Type::Text).Unique().NotNull(), + ColumnBuilder(s_ChangeListenerTable_Column_StartedAt, Type::Int64).NotNull(), + ColumnBuilder(s_ChangeListenerTable_Column_Process, Type::Int64).NotNull(), + }); + + tableBuilder.Execute(m_connection); + + savepoint.Commit(); + } + + void ChangeListenerTable::AddChangeListener(const std::string& objectName) + { + StatementBuilder builder; + builder.InsertInto(s_ChangeListenerTable_Table).Columns({ + s_ChangeListenerTable_Column_ObjectName, + s_ChangeListenerTable_Column_StartedAt, + s_ChangeListenerTable_Column_Process + }).Values( + objectName, + GetCurrentUnixEpoch(), + static_cast<int64_t>(GetCurrentProcessId()) + ); + + builder.Execute(m_connection); + } + + void ChangeListenerTable::RemoveChangeListener(const std::string& objectName) + { + StatementBuilder builder; + builder.DeleteFrom(s_ChangeListenerTable_Table).Where(s_ChangeListenerTable_Column_ObjectName).Equals(objectName); + + builder.Execute(m_connection); + } + + std::vector<std::tuple<std::string, std::chrono::system_clock::time_point, DWORD>> ChangeListenerTable::GetChangeListeners() + { + StatementBuilder builder; + builder.Select({ + s_ChangeListenerTable_Column_ObjectName, + s_ChangeListenerTable_Column_StartedAt, + s_ChangeListenerTable_Column_Process + }).From(s_ChangeListenerTable_Table); + + Statement statement = builder.Prepare(m_connection); + + std::vector<std::tuple<std::string, std::chrono::system_clock::time_point, DWORD>> result; + + while (statement.Step()) + { + result.emplace_back(std::make_tuple(statement.GetColumn<std::string>(0), ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(1)), static_cast<DWORD>(statement.GetColumn<int64_t>(2)))); + } + + return result; + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <winget/SQLiteWrapper.h> +#include <vector> +#include <tuple> + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + struct ChangeListenerTable + { + ChangeListenerTable(AppInstaller::SQLite::Connection& connection); + + // Creates the table. + void Create(); + + // Adds a new change listener to the table. + void AddChangeListener(const std::string& objectName); + + // Removes the change listener with the given name from the table. + void RemoveChangeListener(const std::string& objectName); + + // Gets all change listeners. + std::vector<std::tuple<std::string, std::chrono::system_clock::time_point, DWORD>> GetChangeListeners(); + + private: + AppInstaller::SQLite::Connection& m_connection; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface.h b/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/Schema/IConfigurationDatabase.h" +#include "Database/Schema/0_2/Interface.h" + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + struct Interface : public V0_2::Interface + { + using V0_2::Interface::Interface; + + const AppInstaller::SQLite::Version& GetSchemaVersion() override; + + // Version 0.1 + void InitializeDatabase() override; + void RemoveSet(AppInstaller::SQLite::rowid_t target) override; + + // Version 0.2 + bool MigrateFrom(IConfigurationDatabase* current) override; + void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) override; + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> GetQueueItems() override; + + // Version 0.3 + std::vector<StatusItemTuple> GetStatusSince(int64_t changeIdentifier) override; + std::tuple<int64_t, std::vector<StatusItemTuple>> GetStatusBaseline() override; + void AddListener(const std::string& objectName) override; + void RemoveListener(const std::string& objectName) override; + std::vector<std::tuple<std::string, std::chrono::system_clock::time_point, DWORD>> GetChangeListeners() override; + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) override; + void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) override; + void UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData) override; + ConfigurationSetState GetSetState(const guid& instanceIdentifier) override; + std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier) override; + std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier) override; + std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier) override; + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier) override; + std::optional<std::tuple<HRESULT, std::string, std::string, ConfigurationUnitResultSource>> GetUnitResultInformation(const guid& instanceIdentifier) override; + + private: + // Unconditionally attempts to migrate from the 0.2 base. + void MigrateFrom0_2(); + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface_0_3.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface_0_3.cpp @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Interface.h" +#include "Database/Schema/0_1/SetInfoTable.h" +#include "Database/Schema/0_2/QueueTable.h" +#include "StatusItemTable.h" +#include "ChangeListenerTable.h" +#include <winget/SQLiteMetadataTable.h> + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + static constexpr AppInstaller::SQLite::Version s_InterfaceVersion{ 0, 3 }; + + const AppInstaller::SQLite::Version& Interface::GetSchemaVersion() + { + return s_InterfaceVersion; + } + + void Interface::InitializeDatabase() + { + V0_2::Interface::InitializeDatabase(); + + Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_3"); + MigrateFrom0_2(); + savepoint.Commit(); + } + + void Interface::RemoveSet(AppInstaller::SQLite::rowid_t target) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "RemoveSet_0_3"); + + V0_2::Interface::RemoveSet(target); + + StatusItemTable statusItemTable(*m_storage); + statusItemTable.RemoveForSet(target); + + savepoint.Commit(); + } + + bool Interface::MigrateFrom(IConfigurationDatabase* current) + { + auto currentSchemaVersion = current->GetSchemaVersion(); + if (currentSchemaVersion < s_InterfaceVersion) + { + if (V0_2::Interface::MigrateFrom(current)) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "MigrateFrom0_2"); + + MigrateFrom0_2(); + s_InterfaceVersion.SetSchemaVersion(*m_storage); + + savepoint.Commit(); + + return true; + } + } + else if (currentSchemaVersion == s_InterfaceVersion) + { + return true; + } + + return false; + } + + void Interface::AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) + { + V0_2::QueueTable queueTable(*m_storage); + queueTable.AddQueueItemWithProcess(instanceIdentifier, objectName); + } + + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> Interface::GetQueueItems() + { + V0_2::QueueTable queueTable(*m_storage); + return queueTable.GetQueueItemsWithProcess(); + } + + void Interface::MigrateFrom0_2() + { + V0_2::QueueTable queueTable(*m_storage); + queueTable.AddProcessColumn(); + + ChangeListenerTable changeListenerTable(*m_storage); + changeListenerTable.Create(); + + StatusItemTable statusItemTable(*m_storage); + statusItemTable.Create(); + } + + std::vector<IConfigurationDatabase::StatusItemTuple> Interface::GetStatusSince(int64_t changeIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetStatusSince(changeIdentifier); + } + + std::tuple<int64_t, std::vector<IConfigurationDatabase::StatusItemTuple>> Interface::GetStatusBaseline() + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetStatusBaseline(); + } + + void Interface::AddListener(const std::string& objectName) + { + ChangeListenerTable changeListenerTable(*m_storage); + changeListenerTable.AddChangeListener(objectName); + } + + void Interface::RemoveListener(const std::string& objectName) + { + ChangeListenerTable changeListenerTable(*m_storage); + changeListenerTable.RemoveChangeListener(objectName); + } + + std::vector<std::tuple<std::string, std::chrono::system_clock::time_point, DWORD>> Interface::GetChangeListeners() + { + ChangeListenerTable changeListenerTable(*m_storage); + return changeListenerTable.GetChangeListeners(); + } + + void Interface::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + StatusItemTable statusItemTable(*m_storage); + statusItemTable.UpdateSetState(setInstanceIdentifier, state); + } + + void Interface::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) + { + StatusItemTable statusItemTable(*m_storage); + statusItemTable.UpdateSetInQueue(setInstanceIdentifier, inQueue); + } + + void Interface::UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData) + { + StatusItemTable statusItemTable(*m_storage); + statusItemTable.UpdateUnitState(setInstanceIdentifier, changeData); + } + + ConfigurationSetState Interface::GetSetState(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetSetState(instanceIdentifier); + } + + std::chrono::system_clock::time_point Interface::GetSetFirstApply(const guid& instanceIdentifier) + { + V0_1::SetInfoTable setInfoTable(*m_storage); + return setInfoTable.GetSetFirstApply(instanceIdentifier); + } + + std::chrono::system_clock::time_point Interface::GetSetApplyBegun(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetSetApplyBegun(instanceIdentifier); + } + + std::chrono::system_clock::time_point Interface::GetSetApplyEnded(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetSetApplyEnded(instanceIdentifier); + } + + ConfigurationUnitState Interface::GetUnitState(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetUnitState(instanceIdentifier); + } + + std::optional<std::tuple<HRESULT, std::string, std::string, ConfigurationUnitResultSource>> Interface::GetUnitResultInformation(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetUnitResultInformation(instanceIdentifier); + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.cpp @@ -0,0 +1,365 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "StatusItemTable.h" +#include "Database/Schema/0_1/SetInfoTable.h" +#include <AppInstallerDateTime.h> +#include <AppInstallerLanguageUtilities.h> +#include <AppInstallerStrings.h> +#include <winget/SQLiteStatementBuilder.h> + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + namespace + { + constexpr std::string_view s_StatusItemTable_Table = "status_items"sv; + constexpr std::string_view s_StatusItemTable_ChangeIdentifierIndex = "status_items_change_idx"sv; + constexpr std::string_view s_StatusItemTable_SetRowIdIndex = "status_items_set_idx"sv; + constexpr std::string_view s_StatusItemTable_UnitInstanceIndex = "status_items_unit_idx"sv; + + constexpr std::string_view s_StatusItemTable_Column_ChangeIdentifier = "change_identifier"sv; + constexpr std::string_view s_StatusItemTable_Column_ChangeTimeInitial = "change_time_initial"sv; + constexpr std::string_view s_StatusItemTable_Column_ChangeTimeLatest = "change_time_latest"sv; + constexpr std::string_view s_StatusItemTable_Column_SetRowId = "set_rowid"sv; + constexpr std::string_view s_StatusItemTable_Column_InQueue = "in_queue"sv; + constexpr std::string_view s_StatusItemTable_Column_UnitInstanceIdentifier = "unit_instance_identifier"sv; + constexpr std::string_view s_StatusItemTable_Column_State = "state"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultCode = "result_code"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultDescription = "result_description"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultDetails = "result_details"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultSource = "result_source"sv; + + void BuildBaseStatusSelectStatement(StatementBuilder& builder) + { + builder.Select({ + s_StatusItemTable_Column_ChangeIdentifier, // 0 + s_StatusItemTable_Column_ChangeTimeLatest, // 1 + V0_1::SetInfoTable::InstanceIdentifierColumn(), // 2 + s_StatusItemTable_Column_InQueue, // 3 + s_StatusItemTable_Column_UnitInstanceIdentifier, // 4 + s_StatusItemTable_Column_State, // 5 + s_StatusItemTable_Column_ResultCode, // 6 + s_StatusItemTable_Column_ResultDescription, // 7 + s_StatusItemTable_Column_ResultDetails, // 8 + s_StatusItemTable_Column_ResultSource, // 9 + }).From(s_StatusItemTable_Table).LeftOuterJoin(V0_1::SetInfoTable::TableName()).On(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }, QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }); + } + + IConfigurationDatabase::StatusItemTuple GetTupleFromStatement(Statement& statement) + { + return std::make_tuple( + statement.GetColumn<int64_t>(0), + ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(1)), + statement.GetColumn<GUID>(2), + statement.GetColumn<bool>(3), + statement.GetColumnIsNull(4) ? std::nullopt : std::make_optional<GUID>(statement.GetColumn<GUID>(4)), + statement.GetColumn<int32_t>(5), + statement.GetColumnIsNull(6) ? std::nullopt : std::make_optional<HRESULT>(statement.GetColumn<int32_t>(6)), + statement.GetColumnIsNull(7) ? std::string{} : statement.GetColumn<std::string>(7), + statement.GetColumnIsNull(8) ? std::string{} : statement.GetColumn<std::string>(8), + statement.GetColumnIsNull(9) ? ConfigurationUnitResultSource::None : statement.GetColumn<ConfigurationUnitResultSource>(9) + ); + } + + int64_t GetLatestChangeIdentifier(Connection& connection) + { + StatementBuilder getLatestChangeBuilder; + getLatestChangeBuilder.Select().Column(Aggregate::Max, s_StatusItemTable_Column_ChangeIdentifier).From(s_StatusItemTable_Table); + + Statement getLatestChange = getLatestChangeBuilder.Prepare(connection); + + return (getLatestChange.Step() ? getLatestChange.GetColumn<int64_t>(0) : 0); + } + + int64_t GetNextChangeIdentifier(Connection& connection) + { + return GetLatestChangeIdentifier(connection) + 1; + } + + void UpdateStatus( + Connection& connection, + const GUID& setInstanceIdentifier, + const std::optional<int32_t>& state, + const std::optional<bool>& inQueue, + const std::optional<GUID>& unitInstanceIdentifier = std::nullopt, + const std::optional<int32_t>& resultCode = std::nullopt, + const std::optional<std::string>& resultDescription = std::nullopt, + const std::optional<std::string>& resultDetails = std::nullopt, + const std::optional<ConfigurationUnitResultSource>& resultSource = std::nullopt) + { + static constexpr std::string_view s_alias = "sub_expression"; + + int64_t changeIdentifier = GetNextChangeIdentifier(connection); + int64_t changeTime = GetCurrentUnixEpoch(); + + // Statement like: + // Update status_items set state = 1 from (Select rowid from set_info where instance_identifier = "foo") as sub where status_items.set_rowid = sub.rowid + StatementBuilder updateBuilder; + updateBuilder.Update(s_StatusItemTable_Table).Set(); + + if (state) + { + updateBuilder.Column(s_StatusItemTable_Column_State).Equals(state.value()); + } + + if (inQueue) + { + updateBuilder.Column(s_StatusItemTable_Column_InQueue).Equals(inQueue.value()); + } + + if (resultCode) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultCode).Equals(resultCode.value()); + } + + if (resultDescription) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultDescription).Equals(resultDescription.value()); + } + + if (resultDetails) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultDetails).Equals(resultDetails.value()); + } + + if (resultSource) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultSource).Equals(resultSource.value()); + } + + updateBuilder. + Column(s_StatusItemTable_Column_ChangeIdentifier).Equals(changeIdentifier). + Column(s_StatusItemTable_Column_ChangeTimeLatest).Equals(changeTime). + From().BeginParenthetical(). + Select(RowIDName).From(V0_1::SetInfoTable::TableName()).Where(V0_1::SetInfoTable::InstanceIdentifierColumn()).Equals(setInstanceIdentifier). + EndParenthetical().As(s_alias).Where(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }).Equals(QualifiedColumn{ s_alias, RowIDName }). + And(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_UnitInstanceIdentifier }).Equals(unitInstanceIdentifier); + + updateBuilder.Execute(connection); + + if (connection.GetChanges() == 0) + { + // No change; we need to insert the status row + StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_StatusItemTable_Table).Columns({ + s_StatusItemTable_Column_ChangeIdentifier, + s_StatusItemTable_Column_ChangeTimeInitial, + s_StatusItemTable_Column_ChangeTimeLatest, + s_StatusItemTable_Column_SetRowId, + s_StatusItemTable_Column_InQueue, + s_StatusItemTable_Column_UnitInstanceIdentifier, + s_StatusItemTable_Column_State, + s_StatusItemTable_Column_ResultCode, + s_StatusItemTable_Column_ResultDescription, + s_StatusItemTable_Column_ResultDetails, + s_StatusItemTable_Column_ResultSource, + }).Select(). + Value(changeIdentifier). + Value(changeTime). + Value(changeTime). + Column(QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }). + Value(inQueue.value_or(false)). + Value(unitInstanceIdentifier). + Value(state.value_or(0)). + Value(resultCode). + Value(resultDescription). + Value(resultDetails). + Value(resultSource). + From(V0_1::SetInfoTable::TableName()).Where(QualifiedColumn{ V0_1::SetInfoTable::TableName(), V0_1::SetInfoTable::InstanceIdentifierColumn() }).Equals(setInstanceIdentifier); + + insertBuilder.Execute(connection); + } + } + + Statement PrepareSelectStatusValues(Connection& connection, const std::optional<GUID>& setInstanceIdentifier, const std::optional<GUID>& unitInstanceIdentifier, std::initializer_list<std::string_view> columns) + { + THROW_HR_IF(E_INVALIDARG, (setInstanceIdentifier && unitInstanceIdentifier) || (!setInstanceIdentifier && !unitInstanceIdentifier)); + + StatementBuilder builder; + builder.Select(columns).From(s_StatusItemTable_Table); + + if (setInstanceIdentifier) + { + builder.Join(V0_1::SetInfoTable::TableName()).On(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }, QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }). + Where(QualifiedColumn{ V0_1::SetInfoTable::TableName(), V0_1::SetInfoTable::InstanceIdentifierColumn() }).Equals(setInstanceIdentifier). + And(s_StatusItemTable_Column_UnitInstanceIdentifier).IsNull(); + } + else + { + builder.Where(s_StatusItemTable_Column_UnitInstanceIdentifier).Equals(unitInstanceIdentifier.value()); + } + + return builder.Prepare(connection); + } + } + + StatusItemTable::StatusItemTable(Connection& connection) : m_connection(connection) {} + + void StatusItemTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "StatusItemTable_Create_0_3"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_StatusItemTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_StatusItemTable_Column_ChangeIdentifier, Type::Int64).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_ChangeTimeInitial, Type::Int64).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_ChangeTimeLatest, Type::Int64).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_SetRowId, Type::RowId).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_InQueue, Type::Bool).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_UnitInstanceIdentifier, Type::Blob), + ColumnBuilder(s_StatusItemTable_Column_State, Type::Int).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_ResultCode, Type::Int), + ColumnBuilder(s_StatusItemTable_Column_ResultDescription, Type::Text), + ColumnBuilder(s_StatusItemTable_Column_ResultDetails, Type::Text), + ColumnBuilder(s_StatusItemTable_Column_ResultSource, Type::Int), + }); + + tableBuilder.Execute(m_connection); + + { + StatementBuilder indexBuilder; + indexBuilder.CreateIndex(s_StatusItemTable_ChangeIdentifierIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_ChangeIdentifier); + indexBuilder.Execute(m_connection); + } + + { + StatementBuilder indexBuilder; + indexBuilder.CreateIndex(s_StatusItemTable_SetRowIdIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_SetRowId); + indexBuilder.Execute(m_connection); + } + + { + StatementBuilder indexBuilder; + indexBuilder.CreateUniqueIndex(s_StatusItemTable_UnitInstanceIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_UnitInstanceIdentifier); + indexBuilder.Execute(m_connection); + } + + savepoint.Commit(); + } + + void StatusItemTable::RemoveForSet(AppInstaller::SQLite::rowid_t target) + { + StatementBuilder builder; + builder.DeleteFrom(s_StatusItemTable_Table).Where(s_StatusItemTable_Column_SetRowId).Equals(target); + builder.Execute(m_connection); + } + + std::vector<IConfigurationDatabase::StatusItemTuple> StatusItemTable::GetStatusSince(int64_t changeIdentifier) + { + StatementBuilder builder; + BuildBaseStatusSelectStatement(builder); + builder.Where(s_StatusItemTable_Column_ChangeIdentifier).IsGreaterThan(changeIdentifier).OrderBy(s_StatusItemTable_Column_ChangeIdentifier); + + Statement statement = builder.Prepare(m_connection); + + std::vector<IConfigurationDatabase::StatusItemTuple> result; + + while (statement.Step()) + { + result.emplace_back(GetTupleFromStatement(statement)); + } + + return result; + } + + std::tuple<int64_t, std::vector<IConfigurationDatabase::StatusItemTuple>> StatusItemTable::GetStatusBaseline() + { + int64_t latestChange = GetLatestChangeIdentifier(m_connection); + std::vector<IConfigurationDatabase::StatusItemTuple> setStatus; + + StatementBuilder builder; + BuildBaseStatusSelectStatement(builder); + builder.Where(s_StatusItemTable_Column_UnitInstanceIdentifier).IsNull(); + + Statement statement = builder.Prepare(m_connection); + + while (statement.Step()) + { + setStatus.emplace_back(GetTupleFromStatement(statement)); + } + + return std::make_tuple(latestChange, std::move(setStatus)); + } + + void StatusItemTable::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + UpdateStatus(m_connection, setInstanceIdentifier, AppInstaller::ToIntegral(state), std::nullopt); + } + + void StatusItemTable::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) + { + UpdateStatus(m_connection, setInstanceIdentifier, std::nullopt, inQueue); + } + + void StatusItemTable::UpdateUnitState(const guid& setInstanceIdentifier, const IConfigurationDatabase::ConfigurationSetChangeDataPtr& changeData) + { + const auto& resultInformation = changeData->ResultInformation(); + + std::optional<HRESULT> resultCode; + std::optional<std::string> resultDescription; + std::optional<std::string> resultDetails; + std::optional<ConfigurationUnitResultSource> resultSource; + + if (resultInformation) + { + resultCode = resultInformation.ResultCode(); + resultDescription = ConvertToUTF8(resultInformation.Description()); + resultDetails = ConvertToUTF8(resultInformation.Details()); + resultSource = resultInformation.ResultSource(); + } + + UpdateStatus(m_connection, setInstanceIdentifier, AppInstaller::ToIntegral(changeData->UnitState()), std::nullopt, changeData->Unit().InstanceIdentifier(), resultCode, resultDescription, resultDetails, resultSource); + } + + ConfigurationSetState StatusItemTable::GetSetState(const guid& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_State }); + + return (statement.Step() ? statement.GetColumn<ConfigurationSetState>(0) : ConfigurationSetState::Unknown); + } + + std::chrono::system_clock::time_point StatusItemTable::GetSetApplyBegun(const GUID& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_ChangeTimeInitial }); + + return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(0)) : std::chrono::system_clock::time_point{}); + } + + std::chrono::system_clock::time_point StatusItemTable::GetSetApplyEnded(const GUID& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_ChangeTimeLatest, s_StatusItemTable_Column_InQueue }); + + // Only return the end time if no longer in the queue + if (statement.Step() && !statement.GetColumn<bool>(1)) + { + return ConvertUnixEpochToSystemClock(statement.GetColumn<int64_t>(0)); + } + + return std::chrono::system_clock::time_point{}; + } + + ConfigurationUnitState StatusItemTable::GetUnitState(const guid& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, std::nullopt, instanceIdentifier, { s_StatusItemTable_Column_State }); + + return (statement.Step() ? statement.GetColumn<ConfigurationUnitState>(0) : ConfigurationUnitState::Unknown); + } + + std::optional<std::tuple<HRESULT, std::string, std::string, ConfigurationUnitResultSource>> StatusItemTable::GetUnitResultInformation(const guid& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, std::nullopt, instanceIdentifier, + { s_StatusItemTable_Column_ResultCode, s_StatusItemTable_Column_ResultDescription, s_StatusItemTable_Column_ResultDetails, s_StatusItemTable_Column_ResultSource }); + + if (statement.Step() && !statement.GetColumnIsNull(0)) + { + return statement.GetRow<int32_t, std::string, std::string, ConfigurationUnitResultSource>(); + } + + return std::nullopt; + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.h @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/Schema/IConfigurationDatabase.h" +#include <winget/SQLiteWrapper.h> +#include <vector> +#include <tuple> + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + struct StatusItemTable + { + StatusItemTable(AppInstaller::SQLite::Connection& connection); + + // Creates the table. + void Create(); + + // Removes the status for the target set. + void RemoveForSet(AppInstaller::SQLite::rowid_t target); + + // Gets all status items that have changed since the given change identifier, in order of changes (last item is the new latest change to pass next). + std::vector<IConfigurationDatabase::StatusItemTuple> GetStatusSince(int64_t changeIdentifier); + + // Gets the latest change identifier and the set status items. + std::tuple<int64_t, std::vector<IConfigurationDatabase::StatusItemTuple>> GetStatusBaseline(); + + // Updates a set's state. + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + + // Updates whether a set is in the queue or not. + void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); + + // Updates a unit's state. + void UpdateUnitState(const guid& setInstanceIdentifier, const IConfigurationDatabase::ConfigurationSetChangeDataPtr& changeData); + + // Gets a set's state. + ConfigurationSetState GetSetState(const guid& instanceIdentifier); + + // Gets a set's latest apply begin time. + std::chrono::system_clock::time_point GetSetApplyBegun(const GUID& instanceIdentifier); + + // Gets a set's latest apply end time. + std::chrono::system_clock::time_point GetSetApplyEnded(const GUID& instanceIdentifier); + + // Gets a unit's state. + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + + // Gets a unit's latest result information. + std::optional<std::tuple<HRESULT, std::string, std::string, ConfigurationUnitResultSource>> GetUnitResultInformation(const guid& instanceIdentifier); + + private: + AppInstaller::SQLite::Connection& m_connection; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.cpp b/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.cpp @@ -5,6 +5,7 @@ #include "Database/Schema/0_1/Interface.h" #include "Database/Schema/0_2/Interface.h" +#include "Database/Schema/0_3/Interface.h" namespace winrt::Microsoft::Management::Configuration::implementation { @@ -16,10 +17,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (version.MajorVersion == 0) { - constexpr std::array<std::unique_ptr<IConfigurationDatabase>(*)(const StorageT& s), 2> versionCreatorMap = + constexpr std::array<std::unique_ptr<IConfigurationDatabase>(*)(const StorageT& s), 3> versionCreatorMap = { [](const StorageT& s) { return std::unique_ptr<IConfigurationDatabase>(std::make_unique<Database::Schema::V0_1::Interface>(s)); }, [](const StorageT& s) { return std::unique_ptr<IConfigurationDatabase>(std::make_unique<Database::Schema::V0_2::Interface>(s)); }, + [](const StorageT& s) { return std::unique_ptr<IConfigurationDatabase>(std::make_unique<Database::Schema::V0_3::Interface>(s)); }, }; size_t minorVersion = static_cast<size_t>(version.MinorVersion); @@ -36,7 +38,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation AppInstaller::SQLite::Version IConfigurationDatabase::GetLatestVersion() { - return { 0, 2 }; + return { 0, 3 }; } std::unique_ptr<IConfigurationDatabase> IConfigurationDatabase::CreateFor(const std::shared_ptr<AppInstaller::SQLite::SQLiteDynamicStorage>& storage, bool allowMigration) @@ -66,7 +68,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { } - std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, bool>> IConfigurationDatabase::GetQueueItems() + std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> IConfigurationDatabase::GetQueueItems() { return {}; } @@ -74,4 +76,69 @@ namespace winrt::Microsoft::Management::Configuration::implementation void IConfigurationDatabase::RemoveQueueItem(const std::string&) { } + + std::vector<IConfigurationDatabase::StatusItemTuple> IConfigurationDatabase::GetStatusSince(int64_t) + { + return {}; + } + + std::tuple<int64_t, std::vector<IConfigurationDatabase::StatusItemTuple>> IConfigurationDatabase::GetStatusBaseline() + { + return { 0, {} }; + } + + void IConfigurationDatabase::AddListener(const std::string&) + { + } + + void IConfigurationDatabase::RemoveListener(const std::string&) + { + } + + std::vector<std::tuple<std::string, std::chrono::system_clock::time_point, DWORD>> IConfigurationDatabase::GetChangeListeners() + { + return {}; + } + + void IConfigurationDatabase::UpdateSetState(const guid&, ConfigurationSetState) + { + } + + void IConfigurationDatabase::UpdateSetInQueue(const guid&, bool) + { + } + + void IConfigurationDatabase::UpdateUnitState(const guid&, const ConfigurationSetChangeDataPtr&) + { + } + + ConfigurationSetState IConfigurationDatabase::GetSetState(const guid&) + { + return ConfigurationSetState::Unknown; + } + + std::chrono::system_clock::time_point IConfigurationDatabase::GetSetFirstApply(const guid&) + { + return {}; + } + + std::chrono::system_clock::time_point IConfigurationDatabase::GetSetApplyBegun(const guid&) + { + return {}; + } + + std::chrono::system_clock::time_point IConfigurationDatabase::GetSetApplyEnded(const guid&) + { + return {}; + } + + ConfigurationUnitState IConfigurationDatabase::GetUnitState(const guid&) + { + return ConfigurationUnitState::Unknown; + } + + std::optional<std::tuple<HRESULT, std::string, std::string, ConfigurationUnitResultSource>> IConfigurationDatabase::GetUnitResultInformation(const guid&) + { + return std::nullopt; + } } diff --git a/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.h b/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.h @@ -4,6 +4,7 @@ #include "winrt/Microsoft.Management.Configuration.h" #include "ConfigurationSet.h" #include "ConfigurationUnit.h" +#include "ConfigurationSetChangeData.h" #include <winget/SQLiteVersion.h> #include <winget/SQLiteDynamicStorage.h> #include <wil/cppwinrt_wrl.h> @@ -17,6 +18,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { using ConfigurationSetPtr = winrt::com_ptr<implementation::ConfigurationSet>; using ConfigurationUnitPtr = winrt::com_ptr<implementation::ConfigurationUnit>; + using ConfigurationSetChangeDataPtr = winrt::com_ptr<implementation::ConfigurationSetChangeData>; virtual ~IConfigurationDatabase() = default; @@ -63,9 +65,69 @@ namespace winrt::Microsoft::Management::Configuration::implementation virtual void SetActiveQueueItem(const std::string& objectName); // Gets all queue items in queue order (item at index 0 is active/next). - virtual std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, bool>> GetQueueItems(); + virtual std::vector<std::tuple<GUID, std::string, std::chrono::system_clock::time_point, DWORD, bool>> GetQueueItems(); // Removes the queue item with the given object name. virtual void RemoveQueueItem(const std::string& objectName); + + // Version 0.3 + + // Gets a set from the database. + virtual ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) = 0; + + // The tuple returned for status items. + using StatusItemTuple = std::tuple< + int64_t, + std::chrono::system_clock::time_point, + GUID, + bool, + std::optional<GUID>, + int32_t, + std::optional<HRESULT>, + std::string, + std::string, + ConfigurationUnitResultSource>; + + // Gets all status items that have changed since the given change identifier, in order of changes (last item is the new latest change to pass next). + virtual std::vector<StatusItemTuple> GetStatusSince(int64_t changeIdentifier); + + // Gets the latest change identifier and the set status items. + virtual std::tuple<int64_t, std::vector<StatusItemTuple>> GetStatusBaseline(); + + // Adds a new listener to the database. + virtual void AddListener(const std::string& objectName); + + // Removes a listener from the database. + virtual void RemoveListener(const std::string& objectName); + + // Gets all of the change listeners in the database. + virtual std::vector<std::tuple<std::string, std::chrono::system_clock::time_point, DWORD>> GetChangeListeners(); + + // Updates a set's state. + virtual void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + + // Updates whether a set is in the queue or not. + virtual void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); + + // Updates a unit's state. + virtual void UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData); + + // Gets a set's state. + virtual ConfigurationSetState GetSetState(const guid& instanceIdentifier); + + // Gets a set's first apply time. + virtual std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier); + + // Gets a set's latest apply begin time. + virtual std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); + + // Gets a set's latest apply end time. + virtual std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); + + // Gets a unit's state. + virtual ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + + // Gets a unit's latest result information. + virtual std::optional<std::tuple<HRESULT, std::string, std::string, ConfigurationUnitResultSource>> GetUnitResultInformation(const guid& instanceIdentifier); }; } diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -278,7 +278,7 @@ namespace Microsoft.Management.Configuration // The state of the configuration set for this event (the ConfigurationSet can be used to get the current state, which may be different). ConfigurationSetState SetState{ get; }; - + // The state of the configuration unit for this event (the ConfigurationUnit can be used to get the current state, which may be different). ConfigurationUnitState UnitState{ get; }; @@ -368,7 +368,7 @@ namespace Microsoft.Management.Configuration String SchemaVersion; // Only changes for this set are sent to this event. - // This includes things like: start/stop of the entire set for application or test, start/stop of a unit for application or test. + // This includes things like: start/stop of the entire set for application, start/stop of a unit for application. event Windows.Foundation.TypedEventHandler<ConfigurationSet, ConfigurationSetChangeData> ConfigurationSetChange; // Writes the configuration set to the given stream. @@ -858,7 +858,7 @@ namespace Microsoft.Management.Configuration Boolean GenerateTelemetryEvents; // Only top level configuration changes are sent to this event. - // This includes things like: creation of a new set for intent to run, start/stop of a set for application or test, deletion of a not started set. + // This includes things like: creation of a new set for intent to run, start/stop of a set for application, deletion of a not started set. event Windows.Foundation.TypedEventHandler<ConfigurationSet, ConfigurationChangeData> ConfigurationChange; // Gets the configuration sets that have already been applied or with the intent to be applied (this may include in progress sets or those that are waiting on others). diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -220,6 +220,7 @@ <ClInclude Include="ConfigurationSetSerializer_0_2.h" /> <ClInclude Include="ConfigurationSetUtilities.h" /> <ClInclude Include="ConfigurationStaticFunctions.h" /> + <ClInclude Include="ConfigurationStatus.h" /> <ClInclude Include="ConfigurationUnit.h" /> <ClInclude Include="ConfigurationUnitResultInformation.h" /> <ClInclude Include="Database\ConfigurationDatabase.h" /> @@ -228,6 +229,9 @@ <ClInclude Include="Database\Schema\0_1\UnitInfoTable.h" /> <ClInclude Include="Database\Schema\0_2\Interface.h" /> <ClInclude Include="Database\Schema\0_2\QueueTable.h" /> + <ClInclude Include="Database\Schema\0_3\ChangeListenerTable.h" /> + <ClInclude Include="Database\Schema\0_3\Interface.h" /> + <ClInclude Include="Database\Schema\0_3\StatusItemTable.h" /> <ClInclude Include="Database\Schema\IConfigurationDatabase.h" /> <ClInclude Include="DefaultSetGroupProcessor.h" /> <ClInclude Include="DiagnosticInformationInstance.h" /> @@ -270,6 +274,7 @@ <ClCompile Include="ConfigurationSetSerializer_0_2.cpp" /> <ClCompile Include="ConfigurationSetUtilities.cpp" /> <ClCompile Include="ConfigurationStaticFunctions.cpp" /> + <ClCompile Include="ConfigurationStatus.cpp" /> <ClCompile Include="ConfigurationUnit.cpp" /> <ClCompile Include="ConfigurationUnitResultInformation.cpp" /> <ClCompile Include="Database\ConfigurationDatabase.cpp" /> @@ -278,6 +283,9 @@ <ClCompile Include="Database\Schema\0_1\UnitInfoTable.cpp" /> <ClCompile Include="Database\Schema\0_2\Interface_0_2.cpp" /> <ClCompile Include="Database\Schema\0_2\QueueTable.cpp" /> + <ClCompile Include="Database\Schema\0_3\ChangeListenerTable.cpp" /> + <ClCompile Include="Database\Schema\0_3\Interface_0_3.cpp" /> + <ClCompile Include="Database\Schema\0_3\StatusItemTable.cpp" /> <ClCompile Include="Database\Schema\IConfigurationDatabase.cpp" /> <ClCompile Include="DefaultSetGroupProcessor.cpp" /> <ClCompile Include="DiagnosticInformationInstance.cpp" /> diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -138,6 +138,18 @@ <ClCompile Include="Database\Schema\0_2\QueueTable.cpp"> <Filter>Database\Schema\0_2</Filter> </ClCompile> + <ClCompile Include="ConfigurationStatus.cpp"> + <Filter>Internals</Filter> + </ClCompile> + <ClCompile Include="Database\Schema\0_3\Interface_0_3.cpp"> + <Filter>Database\Schema\0_3</Filter> + </ClCompile> + <ClCompile Include="Database\Schema\0_3\ChangeListenerTable.cpp"> + <Filter>Database\Schema\0_3</Filter> + </ClCompile> + <ClCompile Include="Database\Schema\0_3\StatusItemTable.cpp"> + <Filter>Database\Schema\0_3</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -285,6 +297,18 @@ <ClInclude Include="Database\Schema\0_2\QueueTable.h"> <Filter>Database\Schema\0_2</Filter> </ClInclude> + <ClInclude Include="ConfigurationStatus.h"> + <Filter>Internals</Filter> + </ClInclude> + <ClInclude Include="Database\Schema\0_3\Interface.h"> + <Filter>Database\Schema\0_3</Filter> + </ClInclude> + <ClInclude Include="Database\Schema\0_3\ChangeListenerTable.h"> + <Filter>Database\Schema\0_3</Filter> + </ClInclude> + <ClInclude Include="Database\Schema\0_3\StatusItemTable.h"> + <Filter>Database\Schema\0_3</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Midl Include="Microsoft.Management.Configuration.idl" /> @@ -322,6 +346,9 @@ <Filter Include="Database\Schema\0_2"> <UniqueIdentifier>{f214d0f3-3e9c-469b-91ae-213315d39a69}</UniqueIdentifier> </Filter> + <Filter Include="Database\Schema\0_3"> + <UniqueIdentifier>{d059436d-cd54-4cf2-96bc-4db53c617537}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" /> diff --git a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp @@ -296,7 +296,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation void TelemetryTraceLogger::LogConfigProcessingSummary( const guid& setIdentifier, std::string_view inputHash, - bool fromHistory, ConfigurationUnitIntent runIntent, hresult result, ConfigurationUnitResultSource failurePoint, @@ -310,7 +309,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation "ConfigProcessingSummary", TraceLoggingGuid(setIdentifier, "SetID"), AICLI_TraceLoggingStringView(inputHash, "InputHash"), - TraceLoggingBool(fromHistory, "FromHistory"), + TraceLoggingBool(false, "FromHistory"), // deprecated TraceLoggingInt32(static_cast<int32_t>(runIntent), "RunIntent"), TraceLoggingHResult(result, "Result"), TraceLoggingInt32(static_cast<int32_t>(failurePoint), "FailurePoint"), @@ -325,7 +324,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation "ConfigProcessingSummary", WinGet_EventItem(setIdentifier, "SetID"), WinGet_EventItem(inputHash, "InputHash"), - WinGet_EventItem(fromHistory, "FromHistory"), + WinGet_EventItem(false, "FromHistory"), // deprecated WinGet_EventItem(static_cast<int32_t>(runIntent), "RunIntent"), WinGet_EventItem(result, "Result"), WinGet_EventItem(static_cast<int32_t>(failurePoint), "FailurePoint"), @@ -347,7 +346,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), configurationSet.IsFromHistory(), ConfigurationUnitIntent::Assert, + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Assert, summaryData.Result, summaryData.FailurePoint, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); } CATCH_LOG(); @@ -364,7 +363,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), configurationSet.IsFromHistory(), ConfigurationUnitIntent::Assert, + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Assert, error, ConfigurationUnitResultSource::Internal, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); } CATCH_LOG(); @@ -380,7 +379,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), configurationSet.IsFromHistory(), ConfigurationUnitIntent::Apply, + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Apply, result.ResultCode(), summaryData.FailurePoint, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); } CATCH_LOG(); @@ -397,7 +396,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), configurationSet.IsFromHistory(), ConfigurationUnitIntent::Apply, + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Apply, error, ConfigurationUnitResultSource::Internal, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); } CATCH_LOG(); diff --git a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h @@ -86,7 +86,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation void LogConfigProcessingSummary( const guid& setIdentifier, std::string_view inputHash, - bool fromHistory, ConfigurationUnitIntent runIntent, hresult result, ConfigurationUnitResultSource failurePoint,