# sys.featureFlags：Windows Feature Management 开关

`sys.featureFlags` 是 aardio 对 Windows 内部 Feature Management（特性管理）接口的轻量封装。

它适合处理这类问题：

- Windows 更新后出现的隐藏功能、A/B 测试和灰度发布开关；
- 用 aardio 直接查询、启用、禁用或重置 Windows 系统特性；
- 诊断某个 Windows Feature ID 当前来自 ImageDefault、Service 还是 User 覆盖；
- 需要一次提交多个 Runtime Feature ID 的自动化脚本。

它**不是** DISM 的“Windows 可选功能”接口，也不是普通注册表开关库。

## 导入

```aardio
import sys.featureFlags;
```

目标系统需要 Windows 10 2004（Build 18963）或更高版本。查询通常不需要管理员权限；写入 Runtime 配置时请以管理员权限运行。

## 查询右键菜单相关 Feature

```aardio
import sys.featureFlags;

var ids = [60813048,49453572,61934903];
var features = sys.featureFlags.queryMany(ids);
return features;
```

返回信息包含：

- `featureId`
- `enabledState` / `enabledStateName`
- `priority` / `priorityName`
- `variant`
- `variantPayloadKind` / `variantPayload`
- `isWexpConfiguration`
- `hasSubscriptions`

`59213768` 不属于这组右键菜单特性；不要因为网络文章列出它就一并禁用。

## 启用、禁用、恢复默认

```aardio
import sys.featureFlags;

var menuFeatures = [60813048,49453572,61934903];

// 只读查询：默认不修改系统
var before = sys.featureFlags.queryMany(menuFeatures);

// 禁用新版功能，通常会回到旧菜单路径
// var ok,detail = sys.featureFlags.disable(menuFeatures);

// 启用新版功能
// var ok,detail = sys.featureFlags.enable(menuFeatures);

// 删除 User 覆盖，交回 Windows 默认/服务灰度控制
// var ok,detail = sys.featureFlags.reset(menuFeatures);

return before;
```

`reset` 不是“永久固定旧菜单”。它删除本程序写入的 User 优先级覆盖；如果 Windows 后续通过 Service 或其他优先级下发新版功能，重置后仍可能再次出现。

## 状态和优先级

```aardio
import sys.featureFlags;

var info = sys.featureFlags.query(60813048);
if(info){
    return {
        state = info.enabledStateName;
        priority = info.priorityName;
        changeStamp = info.changeStamp;
    };
}
```

常用状态值：

- `sys.featureFlags.state.default = 0`
- `sys.featureFlags.state.disabled = 1`
- `sys.featureFlags.state.enabled = 2`

默认便捷函数使用 `priority.user = 8`。`ImageDefault`、`EKB`、`ImageDefaultEditionOverride`、`Security` 和 `ImageOverride` 属于 Windows/CBS 管理的不可变优先级，本库拒绝写入。

## 批量更新和 Variant

Runtime 更新使用原生批量 API。完整更新表的 32 字节布局为八个连续的 32 位字段：

```aardio
import sys.featureFlags;

var updates = [
    {
        featureId = 60813048;
        priority = sys.featureFlags.priority.user;
        enabledState = sys.featureFlags.state.enabled;
        enabledStateOptions = sys.featureFlags.stateOptions.none;
        variant = 0;
        variantPayloadKind = sys.featureFlags.variantPayloadKind.none;
        variantPayload = 0;
        operation = sys.featureFlags.operation.featureState;
    }
];

var ok,changeStamp = sys.featureFlags.set(updates);
return {ok=ok;changeStamp=changeStamp};
```

不要把 `variant` 或 `variantPayloadKind` 拆成 `BYTE`/`WORD`；那会改变 Windows 原生 ABI 布局。

## Runtime 与 Boot 的区别

Windows Feature Management 有两个配置存储区：

- `Runtime = 1`：当前系统运行时使用的配置。默认的 `enable/disable/reset` 操作这里；`RtlSetFeatureConfigurations` 可以一次提交多个更新。以 `User` 优先级写入的覆盖通常会保留在系统特性存储中，退出程序或重启 Explorer 不会自动撤销它，正常重启后也通常仍然存在。
- `Boot = 0`：系统启动阶段使用的配置。它不是普通的“持久化开关”，而是用于必须在启动阶段生效的特性；Boot 配置的更新通常要在重启时处理，并可能涉及启动状态和回滚流程。

因此，不写 Boot 并不等于设置不能持久保存。右键菜单这类 Explorer 用户态功能，通常只需要 Runtime 的 User 覆盖，修改后重启 `explorer.exe` 即可看到效果。只有明确知道目标 Feature 必须在系统启动阶段读取时，才考虑 Boot。

### 读取 Boot 配置

```aardio
import sys.featureFlags;

var bootFeatures,changeStamp = sys.featureFlags.queryAll(
    sys.featureFlags.configurationType.boot
);
return {features=bootFeatures;changeStamp=changeStamp};
```

`sys.featureFlags` 核心库只写入 Runtime，不伪造或修改 Boot 存储。这样可以把标准库的职责限定为 Windows 原生运行时配置 API；需要修改 Boot 的应用应单独处理目标系统版本、机器级权限、重启要求和恢复方案。

## 与 sys.qfe 配合检查补丁

```aardio
import sys.featureFlags;
import sys.qfe;

var patch = sys.qfe("KB5132061");
var features = sys.featureFlags.queryMany([60813048,49453572,61934903]);
return {patch=patch;features=features};
```

`sys.qfe` 返回 WMI `Win32_QuickFixEngineering` 的首条匹配记录；补丁已安装不代表对应 Feature 一定已被微软灰度启用。

## 重要限制

- `Rtl*FeatureConfiguration` 是 Windows 内部 API，不是稳定的公开应用开发接口；Windows 更新可能改变 Feature ID 的意义。
- `queryAll()` 只能列出当前存储区已有配置，不能列出系统所有“可用但尚未配置”的 Feature ID。
- Runtime 批量提交由 Windows 原生 API 一次处理；这不代表多个 Feature 在业务层一定具有相同的最终可见时刻。
- 写入失败采用 aardio 风格的多返回值：`null,error,status`。成功通常返回 `true,changeStamp`。
- 建议给实验脚本提供显式的 `status`、`enable`、`disable`、`reset` 模式，默认只读；不要把未知 Feature ID 写入任何持久化存储。