This API is being added to enable the Developer+ configuration scenarios. It enables interacting with configuration sets in three contexts:
- Loading an existing configuration set from a stream
- Loading previously applied configuration sets from the local history
- Authoring a new/editing an existing configuration
These configuration sets are composed of configuration units, which describe the individual configurable items and the values to configure.
Configuration actions consist of:
- Test :: Determining whether the system state matches the described state
- Get :: Extracting the current system state with respect to the configuration scope
- Set :: Applying the described state to the system
This API is also intended to support multiple processes watching for state changes, both for the configuration set lifetimes and the individual configuration unit states.
(Add conceptual documentation that will go to docs.microsoft.com "how to" page if needed)
The state of a configuration set in the configuration history.
Name | Description |
---|---|
Unknown | Primarily used for a configuration set that has not been applied. |
Pending | The configuration set has been recorded into the history, but has not yet begun applying. |
InProgress | The configuration set has begun being applied to the system. |
Completed | The configuration set has completed being applied. |
The state of a configuration unit in the configuration history.
Name | Description |
---|---|
Unknown | Primarily used for a configuration unit that has not been applied. |
Pending | The configuration unit has been recorded into the history, but has not yet begun applying. |
InProgress | The configuration unit has begun being applied to the system. |
Completed | The configuration unit has completed being applied; the result information will contain additional details. |
Skipped | The configuration unit was skipped; the result information will contain additional details on the reason. |
Defines the level of detail probing that is allowed about a configuration unit.
Name | Description |
---|---|
Local | Only reads details from local data. |
Catalog | Will query the catalog information for details, but will not download any modules. |
Download | Will download modules, but not load them. |
Load | Will download and load modules for details. |
Information on a result for a single unit of configuration.
The class is used both in reporting results through the ConfigurationSet.ConfigurationSetChange
event as they occur
and in viewing past results via the ConfigurationUnit.ResultInformation
property on a historical record.
Provides information for a specific configuration unit setting.
The properties on this interface are useful for creating a rich authoring experience.
TODO: Define the meaning/schema for this value
Provides information for a specific configuration unit within the runtime.
The properties on this interface are useful for informing the user about the provenance of the code that is responsible for processing the configuration unit.
A single unit of configuration.
Represents the smallest actionable configuration element.
Creates an empty configuration unit for authoring purposes.
ConfigurationUnit();
Name | Description |
---|---|
UnitName | The name of the unit being configured; not a name for this instance. |
InstanceIdentifier | An identifier used to uniquely identify the instance of a configuration unit on the system. |
Identifier | The identifier name of this instance within the set. This value is referenced by other unit's Dependencies . |
Dependencies | The identifiers of the configuration units that this unit depends on. |
Directives | Contains the values that are for use by the configuration system, related to this unit. |
Settings | Contains the values that are for use by the configuration unit itself. |
Details | Contains information on the origin of the configuration unit. You must call ConfigurationProcessor.Get*DetailsAsync to populate this value. |
State | The current state of the configuration unit. |
ResultInformation | Contains information on the result of the latest attempt to apply the configuration unit. |
ShouldApply | Allows for control over whether this unit should be applied when the set containing it is applied. |
TODO: List of well known directives
The change event type that has occurred for a configuration set change.
Name | Description |
---|---|
Unknown | For future use if the caller is not aware of newer change types. |
SetStateChanged | The state of the configuration set has changed. |
UnitStateChanged | The state of a configuration unit has changed. |
The change data sent about changes to a specific set.
This class is sent to subscribers of the ConfigurationSet.ConfigurationSetChange
event, containing information
about the specific change that occurred.
A configuration set contains a collection of configuration units and details about the set.
Represents a self contained group of configuration units that are operated on together.
Creates an empty configuration set for authoring purposes.
ConfigurationSet();
Loads a configuration set from the given stream.
ConfigurationSet(Windows.Storage.Streams.IInputStream stream);
State changes for this set and it's units are sent to subscribers of this event.
event Windows.Foundation.TypedEventHandler<ConfigurationSet, ConfigurationSetChangeData> ConfigurationSetChange;
Serializes the configuration set to the given output stream.
void Serialize(Windows.Storage.Streams.IOutputStream stream);
Removes the configuration set from the recorded history, if present.
void Remove();
You can use this method to remove a configuration set that is no longer relevant. For example, it may have been for a repository that is no longer being used by the user and future conflicts with it's configuration are not important.
Name | Description |
---|---|
Name | The name of the set; if from a file this could be the file name. |
Origin | The origin of the set; if it came from a repository it could be the remote URL (ex. https://github.com/microsoft/winget-cli.git). |
InstanceIdentifier | An identifier used to uniquely identify the instance of a configuration set on the system. |
State | The state that the set is in. |
InitialIntent | The time that this set was recorded with intent to apply. |
ApplyBegun | The time that this set was last started to be applied. |
ApplyEnded | The time that this set was last finished being applied (does not indicate success). |
ConfigurationUnits | The configuration units that are part of this set. |
Provides access to a specific configuration unit within the runtime.
This interface is the primary mechanism used to actually read and write configuration to the system, but it is not expected that you would use this directly as a consumer of Microsoft.Management.Configuration.
Contains the lifetime of the processing action for a configuration set.
This interface is used to contain the lifetime of a processing action, but it is not expected that you would use this directly as a consumer of Microsoft.Management.Configuration.
Allows different runtimes to provide specialized handling of configuration processing.
It is not expected that you would use this interface directly, but rather the ConfigurationProcessor
class.
Spec note: A separate binary (written by us) will contain the implementation(s) of this interface.
Indicates the importance of diagnostic information.
Name | Description |
---|---|
Verbose | Most useful for debugging scenarios; likely too much for general use. |
Informational | Details that can be useful for understanding what is happening. |
Warning | Indicates some abnormal condition, but that is not expected to impact functionality. |
Error | An error has occurred, but this does not necessarily mean that it will halt the operation. |
Critical | A serious, fatal condition has been encountered. |
Contains diagnostic information from the configuration system that can be passed along to the user or log files. This is not intended as primary information, and is thus not localized.
The type of conflict between configuration sets that was detected.
Name | Description |
---|---|
Unknown | For future use if the caller is not aware of newer conflict types. |
MatchingOrigin | Indicates that the first configuration set has a matching name and origin to the second, which has already been applied. |
IdenticalSetApplied | Indicates that the first configuration set is identical to the second, which has already been applied. |
SettingsConflict | Indicates a conflict between the settings of two configuration units. |
Describes a conflict between a setting of two configuration units.
Describes a conflict between two configuration sets.
Flags to control how a configuration set should be applied to the system.
Name | Description |
---|---|
None | The configuration set should be applied in the default manner. |
DoNotOverwriteMatchingOriginSet | Forces a new configuration set instance to be recorded when the set being applied matches a previous set's origin. The default behavior is to assume that the incoming set is an update to the existing set and overwrite it. |
The configuration set change event type that has occurred.
Name | Description |
---|---|
Unknown | For future use if the caller is not aware of newer change types. |
SetAdded | A new configuration set was recorded in the history with the intent to be applied. |
SetStateChanged | A configuration set has changed state. |
SetRemoved | A configuration set has been removed from the history. |
The change data sent about changes to sets.
The configuration processor is responsible for the interactions with the system.
You must use this class to do anything beyond reading configuration sets. It is the entrypoint for all actions that will interact with the actual system configuration.
Creates a configuration processor using the given configuration factory.
ConfigurationProcessor(IConfigurationProcessorFactory factory);
TODO: Add details on the mechanics of creating the
IConfigurationProcessorFactory
objects that we provide.
Checks for conflicts amongst the configuration sets provided, optionally including the configuration sets already applied to the system.
Windows.Foundation.Collections.IVectorView<ConfigurationConflict> CheckForConflicts(Windows.Foundation.Collections.IVectorView<ConfigurationSet> configurationSets, Boolean includeConfigurationHistory);
Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVectorView<ConfigurationConflict> > CheckForConflictsAsync(Windows.Foundation.Collections.IVectorView<ConfigurationSet> configurationSets, Boolean includeConfigurationHistory);
This method should be used on any configuration set that is opened in order to determine if it would cause a conflict
with previously applied configurations. It should be called after setting the Name
and Origin
in order to determine
if it is a potential update.
Gets the details for all configuration units in a set.
void GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel);
Windows.Foundation.IAsyncAction GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel);
This is a convenience/optimization method that will do the same thing as calling GetUnitDetails(Async)
on each
configuration unit in the set. See GetUnitDetails(Async)
for more information on what it will do.
Gets the details for all configuration units in a set.
void GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel);
Windows.Foundation.IAsyncAction GetUnitDetailsAsync(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel);
This method will get the details about a specific configuration unit and make them available via ConfigurationUnit.Details
.
The detailLevel
parameter allows control over how deeply to probe for details. It is an analog for the amount of
trust to place in the configuration unit processor.
Applies the configuration set state to the system.
ApplyConfigurationSetResult ApplySet(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags);
Windows.Foundation.IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> ApplySetAsync(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags);
Using the async method and it's progress is more efficient than subscribing to the ConfigurationSetChange
event before calling this method.
Tests if the system state matches the state described by the configuration set.
TestConfigurationSetResult TestSet(ConfigurationSet configurationSet);
Windows.Foundation.IAsyncOperationWithProgress<TestConfigurationSetResult, TestConfigurationUnitResult> TestSetAsync(ConfigurationSet configurationSet);
Gets the current configuration unit settings from the system state.
GetConfigurationUnitSettingsResult GetSettings(ConfigurationUnit unit);
Windows.Foundation.IAsyncOperation<GetConfigurationUnitSettingsResult> GetSettingsAsync(ConfigurationUnit unit);
Enables listening to internal diagnostics events for logging purposes.
Signals changes to the set of configuration sets in the history, as well as changes to the state of configuration sets in the history.
Gets the configuration sets from the recorded history.
Windows.Foundation.Collections.IVectorView<ConfigurationSet> GetConfigurationHistory();
Gets the configuration sets that have already been applied or those recorded with the intent to be applied. This may include in progress sets or those that are waiting to be applied.
This sample illustrates some of the expected usage patterns.
using Microsoft.Management.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Storage;
using Windows.Storage.Streams;
namespace ConfigurationSample
{
internal static class Helpers
{
internal static IConfigurationProcessorFactory CreateIConfigurationProcessorFactory()
{
throw new NotImplementedException();
}
internal static ConfigurationSet OpenConfigurationSet(string filePath, ConfigurationProcessor processor)
{
var fileOperation = FileRandomAccessStream.OpenAsync(filePath, FileAccessMode.Read);
fileOperation.AsTask().Wait();
var file = fileOperation.GetResults();
OpenConfigurationSetResult result = processor.OpenConfigurationSet(file);
if (result.Set != null)
{
return result.Set;
}
Console.WriteLine($"Failed opening configuration set: 0x{result.ResultCode:X} at {result.Field}");
return null;
}
internal static void SetWatcher(ConfigurationSet set, ConfigurationSetChangeData data)
{
Console.WriteLine($" - Set: {set.Name} [{set.InstanceIdentifier}]");
Console.WriteLine($" Change: {data.Change}");
Console.WriteLine($" Set State: {data.SetState}");
switch (data.Change)
{
case ConfigurationSetChangeEventType.UnitStateChanged:
Console.WriteLine($" Unit: {data.Unit.UnitName} [{data.Unit.InstanceIdentifier}]");
Console.WriteLine($" Unit State: {data.UnitState}");
if (data.UnitState == ConfigurationUnitState.Completed && data.ResultInformation.ResultCode != null)
{
Console.WriteLine($" Failure: {data.ResultInformation.Description} [{data.ResultInformation.ResultCode.HResult}]");
}
break;
}
}
}
internal class ApplyProgressWatcher
{
private bool isFirstProgress = true;
internal void Watcher(IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData> operation, ConfigurationSetChangeData data)
{
if (isFirstProgress)
{
isFirstProgress = false;
// If our first progress callback contains partial results, output them as if they had been called through progress
ApplyConfigurationSetResult partialResult = operation.GetResults();
foreach (ApplyConfigurationUnitResult unitResult in partialResult.UnitResults)
{
HandleUnitProgress(unitResult.Unit, unitResult.State, unitResult.ResultInformation);
}
}
switch (data.Change)
{
case ConfigurationSetChangeEventType.SetStateChanged:
Console.WriteLine($" - Set State: {data.SetState}");
break;
case ConfigurationSetChangeEventType.UnitStateChanged:
HandleUnitProgress(data.Unit, data.UnitState, data.ResultInformation);
break;
}
}
private void HandleUnitProgress(ConfigurationUnit unit, ConfigurationUnitState state, ConfigurationUnitResultInformation resultInformation)
{
switch (state)
{
case ConfigurationUnitState.Pending:
break;
case ConfigurationUnitState.InProgress:
case ConfigurationUnitState.Completed:
case ConfigurationUnitState.Skipped:
Console.WriteLine($" - Unit: {unit.UnitName} [{unit.InstanceIdentifier}]");
Console.WriteLine($" Unit State: {state}");
if (resultInformation.ResultCode != null)
{
Console.WriteLine($" HRESULT: [0x{resultInformation.ResultCode.HResult:X8}]");
Console.WriteLine($" Reason: {resultInformation.Description}");
}
break;
case ConfigurationUnitState.Unknown:
break;
}
}
}
internal class Program
{
static void LoadAndOutput(string[] args)
{
ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory());
// Open the given configuration file
ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor);
if (configSet == null)
{
return;
}
// Output some of the information from the set
Console.WriteLine($"Configuration Set: {args[1]}");
foreach (ConfigurationUnit unit in configSet.ConfigurationUnits)
{
Console.WriteLine($" - Configuration Unit: {unit.UnitName}");
if (!string.IsNullOrEmpty(unit.Identifier))
{
Console.WriteLine($" Identifier: {unit.Identifier}");
}
Console.WriteLine($" Intent: {unit.Intent}");
IReadOnlyList<string> dependencies = unit.Dependencies;
if (dependencies.Count > 0)
{
Console.WriteLine(" Dependencies:");
foreach (string dependency in dependencies)
{
Console.WriteLine($" {dependency}");
}
}
ValueSet directives = unit.Directives;
if (directives.Count > 0)
{
Console.WriteLine(" Directives:");
foreach (var directive in unit.Directives)
{
Console.WriteLine($" {directive.Key}: {directive.Value}");
}
}
ValueSet settings = unit.Settings;
if (settings.Count > 0)
{
Console.WriteLine(" Settings:");
foreach (var setting in unit.Settings)
{
Console.WriteLine($" {setting.Key}: {setting.Value}");
}
}
}
}
static void LoadAndCheckConflicts(string[] args)
{
// Create the factory and processor
ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory());
// Open the given configuration file
ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor);
if (configSet == null)
{
return;
}
// Set a name and origin for this set so that we can see it in the conflict info
configSet.Name = Path.GetFileName(args[1]);
configSet.Origin = args[1];
// Check for conflicts with existing configurations
List<ConfigurationSet> configSets = new List<ConfigurationSet>() { configSet };
IList<ConfigurationConflict> conflicts = processor.CheckForConflicts(configSets, true);
Console.WriteLine($"Conflicts with Configuration Set: {args[1]}");
foreach (ConfigurationConflict conflict in conflicts)
{
Console.WriteLine($" - Conflict: {conflict.Conflict}");
Console.WriteLine($" First Set: {conflict.FirstSet.Name} [{conflict.FirstSet.Origin}]");
Console.WriteLine($" Second Set: {conflict.SecondSet.Name} [{conflict.SecondSet.Origin}]");
if (conflict.Conflict == ConfigurationConflictType.SettingsConflict)
{
Console.WriteLine($" First Unit: {conflict.FirstUnit.UnitName} [{conflict.FirstUnit.InstanceIdentifier}]");
Console.WriteLine($" Second Unit: {conflict.SecondUnit.UnitName} [{conflict.SecondUnit.InstanceIdentifier}]");
foreach (ConfigurationConflictSetting setting in conflict.Settings)
{
Console.WriteLine($" - Setting: {setting.Name}");
Console.WriteLine($" First Value: {setting.FirstValue}");
Console.WriteLine($" Second Value: {setting.SecondValue}");
}
}
}
}
static void LoadAndApply(string[] args)
{
// Create the factory and processor
ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory());
// Open the given configuration file
ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor);
if (configSet == null)
{
return;
}
Console.WriteLine($"Applying Configuration Set: {args[1]}");
ApplyProgressWatcher watcher = new ApplyProgressWatcher();
var operation = processor.ApplySetAsync(configSet, ApplyConfigurationSetFlags.None);
operation.Progress = watcher.Watcher;
operation.AsTask().Wait();
ApplyConfigurationSetResult result = operation.GetResults();
Console.WriteLine($" - Done: {result.ResultCode.HResult}");
}
static void GetHistoryAndWatchEverything(string[] args)
{
Console.WriteLine("Watching all configuration [press Enter to stop]:");
// Create the factory and processor
ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory());
List<ConfigurationSet> list = new List<ConfigurationSet>();
// Attach to the top level change event
processor.ConfigurationChange += (ConfigurationSet incomingSet, ConfigurationChangeData data) =>
{
int existingSetIndex = -1;
lock (list)
{
for (int i = 0; i < list.Count; ++i)
{
if (list[i].InstanceIdentifier == data.InstanceIdentifier)
{
existingSetIndex = i;
break;
}
}
if (data.Change == ConfigurationChangeEventType.SetAdded || data.Change == ConfigurationChangeEventType.SetStateChanged)
{
if (existingSetIndex == -1)
{
incomingSet.ConfigurationSetChange += Helpers.SetWatcher;
list.Add(incomingSet);
}
}
else // Removed
{
if (existingSetIndex != -1)
{
list[existingSetIndex].ConfigurationSetChange -= Helpers.SetWatcher;
list.RemoveAt(existingSetIndex);
}
}
}
Console.WriteLine($" - Set: {data.InstanceIdentifier}");
Console.WriteLine($" Change: {data.Change}");
};
foreach (ConfigurationSet set in processor.GetConfigurationHistory())
{
int existingSetIndex = -1;
lock (list)
{
for (int i = 0; i < list.Count; ++i)
{
if (list[i].InstanceIdentifier == set.InstanceIdentifier)
{
existingSetIndex = i;
break;
}
}
if (existingSetIndex == -1)
{
set.ConfigurationSetChange += Helpers.SetWatcher;
list.Add(set);
}
}
if (existingSetIndex == -1)
{
Console.WriteLine($" - Set: {set.Name} [{set.InstanceIdentifier}]");
Console.WriteLine($" State: {set.State}");
}
}
// Wait for user to press enter
Console.ReadLine();
}
static void Main(string[] args)
{
var method = typeof(Program).GetMethod(args[0], BindingFlags.NonPublic | BindingFlags.Static);
if (method != null)
{
method.Invoke(null, new object[]{ args });
}
else
{
Console.WriteLine($"{args[0]} is not a sample");
}
}
}
}