mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 10:46:33 +02:00
[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:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user