[DSC] Implement Microsoft.PowerToys.Configure DSCResource & winget support (#30918)

* [DSC] Microsoft.PowerToys.Configure module + winget configuration file support

* f: fix for an incorrect directory id reference

* f: update comment

* f: address review comments

* f: file locksmith bug fix

* f: add explorer preview switches in samples

* f: remove debug

* Sign DSC files

* f: implement docs/samples generator

* [ci]Sign FancyZonesEditorCommon.dll

* Sign DSC files in the Generated folder

* f: address review comments

* f: update usable options

* f: add autogenerated sample

* [Installer] Don't use same GUID for different components

* [Installer]Don't remove folders shared by other modules

* Allow configuring PTRun MaximumNumberOfResults

* Remove all settings DSC sample. Just random data

* Allow configuring Hosts Run as Administrator

* Revert "[Installer]Don't remove folders shared by other modules"

This reverts commit 6da3d6cfd5.

* Add all PTRun plugins and Global and keyboard to DSC sample

* Fix issues with context menu modules not disabling

* Fix default enabled values when setting with DSC

* Fix tests regarding default modules in Settings

* Fix merge error

* Restart PowerToys process if we stopped it

---------

Co-authored-by: Andrey Nekrasov <1828123+yuyoyuppe@users.noreply.github.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
Andrey Nekrasov
2024-04-02 01:09:47 +02:00
committed by GitHub
parent 818d3e3035
commit f23fa3f592
81 changed files with 2608 additions and 265 deletions

View File

@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Reflection;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
namespace Microsoft.PowerToys.Settings.UI.Library;
public class CommandLineUtils
{
private static Type GetSettingsConfigType(string moduleName, Assembly settingsLibraryAssembly)
{
var settingsClassName = moduleName == "GeneralSettings" ? moduleName : moduleName + "Settings";
return settingsLibraryAssembly.GetType(typeof(CommandLineUtils).Namespace + "." + settingsClassName);
}
public static ISettingsConfig GetSettingsConfigFor(string moduleName, ISettingsUtils settingsUtils, Assembly settingsLibraryAssembly)
{
return GetSettingsConfigFor(GetSettingsConfigType(moduleName, settingsLibraryAssembly), settingsUtils);
}
/// Executes SettingsRepository<moduleSettingsType>.GetInstance(settingsUtils).SettingsConfig
public static ISettingsConfig GetSettingsConfigFor(Type moduleSettingsType, ISettingsUtils settingsUtils)
{
var genericSettingsRepositoryType = typeof(SettingsRepository<>);
var moduleSettingsRepositoryType = genericSettingsRepositoryType.MakeGenericType(moduleSettingsType);
// Note: GeneralSettings is only used here only to satisfy nameof constrains, i.e. the choice of this particular type doesn't have any special significance.
var getInstanceInfo = moduleSettingsRepositoryType.GetMethod(nameof(SettingsRepository<GeneralSettings>.GetInstance));
var settingsRepository = getInstanceInfo.Invoke(null, new object[] { settingsUtils });
var settingsConfigProperty = getInstanceInfo.ReturnType.GetProperty(nameof(SettingsRepository<GeneralSettings>.SettingsConfig));
return settingsConfigProperty.GetValue(settingsRepository) as ISettingsConfig;
}
public static Assembly GetSettingsAssembly()
{
return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "PowerToys.Settings.UI.Lib");
}
public static object GetPropertyValue(string propertyName, ISettingsConfig settingsConfig)
{
var (settingInfo, properties) = LocateSetting(propertyName, settingsConfig);
return settingInfo.GetValue(properties);
}
public static object GetProperties(ISettingsConfig settingsConfig)
{
var settingsType = settingsConfig.GetType();
if (settingsType == typeof(GeneralSettings))
{
return settingsConfig;
}
var settingsConfigInfo = settingsType.GetProperty("Properties");
return settingsConfigInfo.GetValue(settingsConfig);
}
public static (PropertyInfo SettingInfo, object Properties) LocateSetting(string propertyName, ISettingsConfig settingsConfig)
{
var properties = GetProperties(settingsConfig);
var propertiesType = properties.GetType();
if (propertiesType == typeof(GeneralSettings) && propertyName.StartsWith("Enabled.", StringComparison.InvariantCulture))
{
var moduleNameToToggle = propertyName.Replace("Enabled.", string.Empty);
properties = propertiesType.GetProperty("Enabled").GetValue(properties);
propertiesType = properties.GetType();
propertyName = moduleNameToToggle;
}
return (propertiesType.GetProperty(propertyName), properties);
}
public static PropertyInfo GetSettingPropertyInfo(string propertyName, ISettingsConfig settingsConfig)
{
return LocateSetting(propertyName, settingsConfig).SettingInfo;
}
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using System.Xml;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library;
/// <summary>
/// This user flow allows DSC resources to use PowerToys.Settings executable to get settings values by querying them from command line using the following syntax:
/// PowerToys.Settings.exe get <path to a json file containing a list of modules and their corresponding properties>
///
/// Example: PowerToys.Settings.exe get %TEMP%\properties.json
/// `properties.json` file contents:
/// {
/// "AlwaysOnTop": ["FrameEnabled", "FrameAccentColor"],
/// "FancyZones": ["FancyzonesShiftDrag", "FancyzonesShowOnAllMonitors"]
/// }
///
/// Upon PowerToys.Settings.exe completion, it'll update `properties.json` file to contain something like this:
/// {
/// "AlwaysOnTop": {
/// "FrameEnabled": true,
/// "FrameAccentColor": "#0099cc"
/// },
/// "FancyZones": {
/// "FancyzonesShiftDrag": true,
/// "FancyzonesShowOnAllMonitors": false
/// }
/// }
/// </summary>
public sealed class GetSettingCommandLineCommand
{
private static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
public static string Execute(Dictionary<string, List<string>> settingNamesForModules)
{
var modulesSettings = new Dictionary<string, Dictionary<string, object>>();
var settingsAssembly = CommandLineUtils.GetSettingsAssembly();
var settingsUtils = new SettingsUtils();
var enabledModules = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils).SettingsConfig.Enabled;
foreach (var (moduleName, settings) in settingNamesForModules)
{
var moduleSettings = new Dictionary<string, object>();
if (moduleName != nameof(GeneralSettings))
{
moduleSettings.Add("Enabled", typeof(EnabledModules).GetProperty(moduleName).GetValue(enabledModules));
}
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsAssembly);
foreach (var settingName in settings)
{
var value = CommandLineUtils.GetPropertyValue(settingName, settingsConfig);
if (value != null)
{
var cmdReprValue = ICmdLineRepresentable.ToCmdRepr(value.GetType(), value);
moduleSettings.Add(settingName, cmdReprValue);
}
}
modulesSettings.Add(moduleName, moduleSettings);
}
return JsonSerializer.Serialize(modulesSettings, _serializerOptions);
}
}

View File

@@ -103,6 +103,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
return LayoutMap.GetKeyName(key);
}
public static uint GetKeyValue(string key)
{
return LayoutMap.GetKeyValue(key);
}
public static string GetProductVersion()
{
return interop.CommonManaged.GetProductVersion();

View File

@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library;
/// <summary>
/// This user flow allows DSC resources to use PowerToys.Settings executable to set custom settings values by suppling them from command line using the following syntax:
/// PowerToys.Settings.exe setAdditional <module struct name> <path to a json file containing the properties>
/// </summary>
public sealed class SetAdditionalSettingsCommandLineCommand
{
private static readonly string KeyPropertyName = "Name";
private static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
};
private struct AdditionalPropertyInfo
{
public string RootPropertyName;
public JsonValueKind RootObjectType;
}
private static readonly Dictionary<string, AdditionalPropertyInfo> SupportedAdditionalPropertiesInfoForModules = new Dictionary<string, AdditionalPropertyInfo> { { "PowerLauncher", new AdditionalPropertyInfo { RootPropertyName = "Plugins", RootObjectType = JsonValueKind.Array } } };
private static void ExecuteRootArray(JsonElement.ArrayEnumerator properties, IEnumerable<object> currentPropertyValuesArray)
{
// In case it's an array of object -> combine the existing values with the provided
var currentPropertyValueType = currentPropertyValuesArray.FirstOrDefault()?.GetType();
object matchedElement = null;
foreach (var arrayElement in properties)
{
var newElementPropertyValues = new Dictionary<string, object>();
foreach (var elementProperty in arrayElement.EnumerateObject())
{
var elementPropertyName = elementProperty.Name;
var elementPropertyType = currentPropertyValueType.GetProperty(elementPropertyName).PropertyType;
var elemePropertyValue = ICmdLineRepresentable.ParseFor(elementPropertyType, elementProperty.Value.ToString());
if (elementPropertyName == KeyPropertyName)
{
foreach (var currentElementValue in currentPropertyValuesArray)
{
var currentElementType = currentElementValue.GetType();
var keyPropertyNameInfo = currentElementType.GetProperty(KeyPropertyName);
var keyPropertyValue = keyPropertyNameInfo.GetValue(currentElementValue);
if (string.Equals(keyPropertyValue, elemePropertyValue))
{
matchedElement = currentElementValue;
break;
}
}
}
else
{
newElementPropertyValues.Add(elementPropertyName, elemePropertyValue);
}
}
if (matchedElement != null)
{
foreach (var overriddenProperty in newElementPropertyValues)
{
var propertyInfo = currentPropertyValueType.GetProperty(overriddenProperty.Key);
propertyInfo.SetValue(matchedElement, overriddenProperty.Value);
}
}
}
}
public static void Execute(string moduleName, JsonDocument settings, ISettingsUtils settingsUtils)
{
Assembly settingsLibraryAssembly = CommandLineUtils.GetSettingsAssembly();
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsLibraryAssembly);
var settingsConfigType = settingsConfig.GetType();
if (!SupportedAdditionalPropertiesInfoForModules.TryGetValue(moduleName, out var additionalPropertiesInfo))
{
return;
}
var propertyValueInfo = settingsConfigType.GetProperty(additionalPropertiesInfo.RootPropertyName);
var currentPropertyValue = propertyValueInfo.GetValue(settingsConfig);
// For now, only a certain data shapes are supported
switch (additionalPropertiesInfo.RootObjectType)
{
case JsonValueKind.Array:
ExecuteRootArray(settings.RootElement.EnumerateArray(), currentPropertyValue as IEnumerable<object>);
break;
default:
throw new NotImplementedException();
}
settingsUtils.SaveSettings(settingsConfig.ToJsonString(), settingsConfig.GetModuleName());
}
}

View File

@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Reflection;
using Settings.UI.Library.Attributes;
namespace Microsoft.PowerToys.Settings.UI.Library;
/// <summary>
/// This user flow allows DSC resources to use PowerToys.Settings executable to set settings values by suppling them from command line using the following syntax:
/// PowerToys.Settings.exe set <module struct name>.<field name> <field_value>
///
/// Example: PowerToys.Settings.exe set MeasureTool.MeasureCrossColor "#00FF00"
/// </summary>
public sealed class SetSettingCommandLineCommand
{
private static readonly char[] SettingNameSeparator = { '.' };
private static (string ModuleName, string PropertyName) ParseSettingName(string settingName)
{
var parts = settingName.Split(SettingNameSeparator, 2, StringSplitOptions.RemoveEmptyEntries);
return (parts[0], parts[1]);
}
public static void Execute(string settingName, string settingValue, ISettingsUtils settingsUtils)
{
Assembly settingsLibraryAssembly = CommandLineUtils.GetSettingsAssembly();
var (moduleName, propertyName) = ParseSettingName(settingName);
var settingsConfig = CommandLineUtils.GetSettingsConfigFor(moduleName, settingsUtils, settingsLibraryAssembly);
var propertyInfo = CommandLineUtils.GetSettingPropertyInfo(propertyName, settingsConfig);
if (propertyInfo == null)
{
throw new ArgumentException($"Property '{propertyName}' wasn't found");
}
if (propertyInfo.PropertyType.GetCustomAttribute<CmdConfigureIgnoreAttribute>() != null)
{
throw new ArgumentException($"Property '{propertyName}' is explicitly ignored");
}
// Execute settingsConfig.Properties.<propertyName> = settingValue
var propertyValue = ICmdLineRepresentable.ParseFor(propertyInfo.PropertyType, settingValue);
var (settingInfo, properties) = CommandLineUtils.LocateSetting(propertyName, settingsConfig);
settingInfo.SetValue(properties, propertyValue);
settingsUtils.SaveSettings(settingsConfig.ToJsonString(), settingsConfig.GetModuleName());
}
}