首页
/ PowerToys DSC 配置详解:用 winget configure 一键安装并声明式管理 PowerToys 设置

PowerToys DSC 配置详解:用 winget configure 一键安装并声明式管理 PowerToys 设置

2026-09-06 20:17:01作者:牧宁李

PowerToys 提供了 PowerToysConfigure 这个基于类的 DSC(Desired State Configuration)资源,允许管理员通过 winget configure 命令在同一个配置文件里完成“安装 PowerToys + 下发各项设置”的全部工作。本文以仓库内 DSC 配置开发文档 为主线,结合 PowerToys.Settings.DSC.Schema.Generator 的代码生成器源码与 DSC 示例配置文件,完整拆解该资源的工作原理、模块代码是如何由反射自动生成的,以及一套可直接照做的本地调试流程。读完后,你可以自行编写 WinGet 配置(.winget)文件批量管理 PowerToys 设置,并能在本地对生成的 DSC 模块做调试与验证。

一、目标:用 winget configure 安装并配置 PowerToys

该功能的设计目标是让用户使用 winget configure 命令,配合一份 WinGet 配置文件(YAML 格式),同时完成 PowerToys 的安装与设置项下发。文档给出的最小示例如下:

properties:
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      directives:
        description: Install PowerToys
        allowPrerelease: true
      settings:
        id: PowerToys (Preview)
        source: winget

    - resource: PowerToysConfigure
      directives:
        description: Configure PowerToys
      settings:
        ShortcutGuide:
          Enabled: false
          OverlayOpacity: 1
        FancyZones:
          Enabled: true
          FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
  configurationVersion: 0.2.0

执行后,第一个资源(Microsoft.WinGet.DSC/WinGetPackage)负责安装 PowerToys,第二个资源(PowerToysConfigure)即可在同一文件中被使用,按 settings 节点中声明的模块名与属性名逐项下发配置。

仓库中的 installAndConfiguration.winget 是这一场景的完整示例,它比文档示例更进一步:通过 id: installPowerToysdependsOn 显式声明了“先安装、后配置”的依赖顺序,并演示了嵌套对象数组(ImageResizer 的自定义尺寸列表)这类复杂设置:

# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
properties:
  resources:
    - resource: Microsoft.WinGet.DSC/WinGetPackage
      id: installPowerToys
      directives:
        description: Install PowerToys
        allowPrerelease: true
      settings:
        id: Microsoft.PowerToys
        source: winget

    - resource: Microsoft.PowerToys.Configure/PowerToysConfigure
      dependsOn:
        - installPowerToys
      directives:
        description: Configure PowerToys
      settings:
        ShortcutGuide:
          Enabled: false
          OverlayOpacity: 50
        FancyZones:
          Enabled: true
          FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F"
        FileLocksmith:
          Enabled: false
        ImageResizer:
          ImageResizerSizes:
            - Name: Square2x
              Width: 200
              Height: 200
              Unit: "Percent"
              Fit: "Stretch"
            - Name: MyInchSize
              Width: 1024
              Height: 1024
              Unit: "Inch"
              Fit: "Fit"

  configurationVersion: 0.2.0

examples 目录 中还提供了 configuration.winget(仅修改设置)、enableAllModules.wingetdisableAllModules.winget(全量启用/禁用模块)、configureLauncherPlugins.winget(配置 PowerToys Run 插件)等可直接参考的样例。

二、工作原理:PowerToysConfigure 如何写入设置

2.1 基于类的 DSC 资源与“是否被指定”的判定

PowerToysConfigure 是一个基于类的 DSC 资源。它需要区分“用户显式指定了某设置”和“用户根本没写这项”,判定方式是:属性值为 $null(字符串/布尔等)或枚举取值为 0,即视为未指定,跳过该项。只有被指定的属性才会触发一次 PowerToys.Settings.exe 调用:

PowerToys.Settings.exe set <ModuleName>.<SettingName> <SettingValue>

对应上面文档中的示例配置,实际会产生 3 次调用:

PowerToys.Settings.exe set ShortcutGuide.Enabled false
PowerToys.Settings.exe set FancyZones.Enabled true
PowerToys.Settings.exe set FancyZones.FancyzonesEditorHotkey "Shift+Ctrl+Alt+F"

从源码结构看,这种“缺省哨兵值”的设计在生成器中有明确注释。DSCGeneration.cs 在输出枚举定义时,会让每个枚举的第一个成员从 1 开始取值:

// Nullable enums seem to be not supported by winget, so the workaround is
// to always start with '1', because by default the values are initialized
// to zero. That allows us to use zero as a "lack of value" indicator.

PropertyEmitInfo 则定义了各 C# 类型到 PowerShell DSC 属性的映射规则:整型映射为 [int](默认值 $null,用 -ne $null 比较)、布尔映射为 [bool]、枚举映射为具名枚举类型(默认值 0)、字符串等其余类型统一映射为 [string](默认值 '',用 -notlike '' 比较)。这就是文档中“检查是否为 $null,枚举检查是否为 0”的底层来源。

2.2 Set/Get 的完整执行流程

生成出来的 PowerToysConfigure 类实现了 DSC 的 Get/Test/Set 三个方法,其逻辑可以在 DSCGeneration.cs 的模板中看到:

  • Get:把请求的模块/属性列表转成 JSON 写入临时文件,调用 PowerToys.Settings.exe get "<临时文件>",再读回文件并反序列化为当前状态对象。
  • Test:始终返回 $false——源码注释说明,因为修改部分设置会产生外部副作用,必须假定配置尚未应用,从而保证 winget configure 每次都会真正执行 Set。
  • Set:先收集所有“被指定”的属性(每个模块类的 ApplyChanges 方法负责把 set <Module>.<Property> "value" 追加到变更列表),然后先停止 PowerToys.SettingsPowerToys 主进程,逐条调用 PowerToys.Settings.exe set ... 完成写入;如果主进程原本在运行,最后再将其重启。

另外,模块的 Enabled 属性在 DSC 模型里是挂在各模块对象上([DscProperty(Key)]),但底层实际映射到 General.Enabled.<ModuleName> 的布尔位;configuration.wingetShortcutGuide: { Enabled: false } 即通过这条路径生效。对于 PowerLauncher 的 Plugins 与 ImageResizer 的 ImageResizerSizes 这类“附加对象数组”属性(见 DSCGeneration.cs 中 AdditionalPropertiesInfoPerModule),生成器会将其序列化为 JSON 写入临时文件,再以 setAdditional <Module> "<临时文件>" 的形式下发,避免在命令行上拼装复杂结构。

2.3 值类型转换:反射 + ICmdReprParsable

PowerToys.Settings 收到 set 命令后,利用 .NET 反射确定 SettingName 对应的属性类型,并把命令行传入的字符串按该类型转换。对于自定义类型(如快捷键、尺寸),项目定义了 ICmdReprParsable 接口约定字符串表示与解析逻辑。ICmdReprParsableTests.cs 中的单元测试展示了这些解析规则:

// 快捷键:修饰键大小写不敏感,支持 0x 十六进制键码
KeyboardKeysProperty.TryParseFromCmd("win+ctrl+Alt+sHifT+Q", out var hotkey);
// => HotkeySettings(true, true, true, true, 0x51)
KeyboardKeysProperty.TryParseFromCmd("shift+ALT+0x59", out var hotkey);

// 布尔:大小写不敏感
BoolProperty.TryParseFromCmd("True", out var result);

// 整数
IntProperty.TryParseFromCmd("123", out var result);

// 自定义尺寸:"宽x高"
MouseJumpThumbnailSize.TryParseFromCmd("1920x1080", out var result);
// => Width = 1920, Height = 1080

也就是说,YAML 里 FancyzonesEditorHotkey: "Shift+Ctrl+Alt+F" 这种字符串,最终会经过同一套 TryParseFromCmd 语义解析为 HotkeySettings,再写入设置存储。

三、模块代码如何生成:PowerToys.Settings.DSC.Schema.Generator

文档明确指出:PowerToysConfigure.psm1PowerToysConfigure.psd1 的大部分内容由 PowerToys.Settings.DSC.Schema.Generator 生成,生成器同样使用 .NET 反射来检视 PowerToys.Settings.UI.Lib.dll 程序集,为每个模块输出对应的 DSC 属性;实际生成动作作为 PowerToys.Settings.DSC.Schema.Generator.csproj 的 post-build 动作执行:

<Target Name="PostBuildAction" AfterTargets="Build"
        Outputs="$(GeneratedDSCModule)" Condition="'$(Platform)'!='ARM64'">
    <Exec Command="...Generator.exe ...PowerToys.Settings.UI.Lib.dll $(GeneratedDSCModule) $(GeneratedDSCManifest)" />
</Target>

生成物落在 src/dsc/Microsoft.PowerToys.Configure/Generated/Microsoft.PowerToys.Configure/ 下的版本子目录中(构建机固定以 x64 生成器运行,ARM64 平台跳过该目标)。

3.1 生成器的三种输出模式

Program.cs 的入口逻辑看,生成器按输出路径的扩展名决定模式:

  • 默认(psm1):输出 Microsoft.PowerToys.Configure.psm1.psd1 模块文件;
  • .md:文档模式,输出 DSC 配置参考文档;
  • .yaml:示例模式,输出一份可运行的样例配置。

命令行用法为:Generator.exe <PowerToys.Settings.UI.Lib.dll 路径> <模块输出路径> [manifest 输出路径]

3.2 反射规则:什么样的类型会成为 DSC 模块

Introspection.cs 定义了扫描规则:

  • ParseModuleSettings 遍历程序集中所有以 Settings 结尾的类,要求其同时具备一个 Properties 属性(类类型)和一个 public const string ModuleName 字段,模块名取类型名去掉 Settings 后缀;
  • ParseProperties 逐个检查属性,带有 JsonIgnoreAttributeCmdConfigureIgnoreAttribute 的属性会被排除在 DSC 之外。CmdConfigureIgnoreAttribute 是专门用于“该设置不适合命令行/DSC 配置”的标记,例如 GeneralSettings.csMouseWithoutBordersProperties.cs 中多处用它屏蔽敏感或不适合批量下发的属性;
  • ParseGeneralSettings 单独识别 GeneralSettings,其属性构成各模块 Enabled 的底层映射。

因此,给某个模块新增一个可通过 DSC 配置的设置,通常只需在其 *Settings/*Properties 类中新增一个符合支持的类型的属性;生成器会在下一次构建时自动将其暴露到 PowerToysConfigure 资源上。

四、调试 DSC 资源的实操流程

文档给出的完整调试步骤如下(原文即面向开发者的 PowerShell 命令序列,可按顺序复现):

1. 环境准备:确认已安装 PowerShell 7.4+,然后安装 DSC 模块:

Install-Module -Name PSDesiredStateConfiguration -RequiredVersion 2.0.7

2. 让 DSC 发现本地模块:打开新的 pwsh 会话,cdsrc\dsc\Microsoft.PowerToys.Configure\Generated 目录(该目录下应有生成好的 Microsoft.PowerToys.Configure.psm1.psd1,位于 ...\Generated\Microsoft.PowerToys.Configure\<版本>\ 子目录,文档中以 0.0.1 为例),然后:

$env:PSModulePath += ";$pwd"

这利用 PSModulePath 的模块发现机制让 DSC 找到我们的模块。验证发现结果:

Get-Module -ListAvailable | grep PowerToys
Get-DSCResource | grep PowerToys

3. 强制导入与直接调用:如果自动发现失败,可强制导入模块以定位问题:

Import-Module .\Microsoft.PowerToys.Configure.psd1

导入成功后,可以不经过 winget 直接调用资源(注意指定了 Debug 选项):

Invoke-DscResource -Name PowerToysConfigure -Method Set -ModuleName Microsoft.PowerToys.Configure -Property @{ Debug = $true; Awake = @{ Enabled = $false; Mode = "TIMED"; IntervalMinutes = "10" } }

指定 Debug = $true 后,资源会在 %TEMP\PowerToys.DSC.TestConfigure.txt 中写入本次下发的属性、当前时间戳及其他调试输出,便于比对“DSC 认为要下发什么”与“实际执行了什么”。

4. 用 winget 做端到端验证:最后用真实配置走一遍完整链路:

winget configure .\configuration.winget --accept-configuration-agreements --disable-interactivity

这里可直接使用仓库自带的 configuration.winget 样例(修改 ShortcutGuide、FancyZones、FileLocksmith 三处设置),或 installAndConfiguration.winget(安装 + 配置一条龙)。

五、延伸阅读:仓库中的 DSC v3 实现

除本文主角 v2 类资源外,仓库 src/dsc/v3 目录还包含面向 DSC v3 命令行协议的 PowerToys.DSC 命令行资源实现,其中 SettingsResource 为每个模块生成独立的 settings 资源,并内置了 get/set/test/export/manifest/schema 等命令。值得注意的是,该实现显式排除了一些模块——源码注释说明 MouseWithoutBorders 含敏感配置值、PowerLauncher 与 NewPlus 的设置使用绝对路径、跨系统不可移植,因此不支持导出/导入。这从侧面印证了前文通过 CmdConfigureIgnoreAttribute 过滤属性的设计意图:并非所有设置都适合声明式批量管理。更多面向用户的 DSC 文档入口可参考仓库内 doc/dsc/overview.md

小结

  • PowerToysConfigure 是基于类的 DSC 资源,通过 PowerToys.Settings.exe set <Module>.<Setting> <Value> 逐项写入设置,以 $null/0/'' 作为“未指定”的哨兵值;
  • 资源模块的绝大部分代码由 PowerToys.Settings.DSC.Schema.Generator 在构建后基于对 PowerToys.Settings.UI.Lib.dll 的反射自动生成,JsonIgnoreCmdConfigureIgnore 属性会被自动排除;
  • 自定义类型(快捷键、尺寸等)依赖 ICmdReprParsable 的字符串解析约定,其行为有 单元测试 覆盖;
  • 本地调试依赖 PowerShell 7.4+ 与 PSDesiredStateConfiguration 2.0.7,通过 PSModulePath 暴露生成目录、Invoke-DscResource(带 Debug)与 winget configure 三层验证,即可完整复现从模块发现到端到端配置的全过程。
登录后查看全文
热门项目推荐
相关项目推荐