- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
本文围绕 Windows-classic-samples 仓库中的 FilterProvider 样例 展开,讲解如何在 Windows Deployment Services(WDS)的 PXE Server 上,用 C++ 编写一个"过滤器型" PXE Provider DLL:它在有序的 Provider 列表最前端充当"闸门",根据一个纯文本 .ini 文件中的策略决定哪些 PXE 客户端被放行给下游(如 WDSDCPXE/BINL)继续服务、哪些被直接拒绝。读完本文,你将掌握 PXE Provider 的注册表注册方式、PxeProviderInitialize/PxeProviderRecvRequest等核心回调的写法、配置文件格式以及完整的端到端部署流程。
WDS 与 PXE Provider 插件模型:过滤器 Provider 的定位
WDS 网络安装场景
Windows Deployment Services(WDS)用于通过网络完成 Windows 操作系统的批量部署:客户端机器可以基于网络启动(PXE Boot)进入 Windows PE 安装环境,从而无需管理员逐台访问每台机器、也无需使用 CD/DVD 介质逐台安装。WDS 服务器承担了引导映像分发、安装映像管理以及 PXE 网络引导等核心职责。
PXE Server 与 PXE Provider 双层结构
从实现上看,WDS 的 PXE Server 可以被拆分为两个部分:PXE Server与PXE Provider(插件):
- PXE Server负责核心网络能力,包括监听网络、解析 DHCP/PXE 报文、维护与客户端的网络会话等;
- PXE Provider是一组插件(plug-in),官方文档将其称为"PXE Providers"。PXE Server 对外暴露插件接口,允许开发者在复用同一套 PXE Server 核心网络代码的基础上,开发自定义的 PXE 解决方案。
标准 WDS 服务器自带WDSDCPXE提供程序(负责将请求交给 BINL 等上层服务处理)。插件模型的意义在于:你可以编写自己的 Provider DLL,与WDSDCPXE并存运行,甚至替换它。
过滤器 Provider:Provider 列表中的"闸门"
在注册表中,PXE Provider 按ProvidersOrder组成一个有序列表,WDSPXE 会把进来的 PXE 请求依次交给列表中的 Provider。
本样例实现的是一个filter provider(过滤器提供程序):它被放在列表最前面,位于WDSDCPXE(或任何其他 PXE Provider)之前,像一个"闸门":
- 如果希望自己的 Provider总是拥有应答 PXE 请求的第一机会,就应把它排在列表首位;
- 过滤器 Provider 自身不提供真正的引导/下载服务,它只裁决"这个客户端的请求是否允许流向下一个 Provider";
- 与标准实现把数据存放在 Active Directory 中不同,本样例把过滤数据存放在一个纯文本文件里(见 cpp/README.txt),便于直接查看和修改。
样例文件清单与工程构成
样例位于 Samples/WindowsDeploymentServices/FilterProvider,cpp子目录下共 5 个文件,各自职责如下:
| 文件 | 说明 |
|---|---|
| FilterProv.cpp | 主 DLL 源码文件,包含全部必需的 DLL 入口点与辅助函数实现 |
| FilterProv.h | 头文件,包含所需头文件并定义辅助数据结构与常量 |
| FilterProv.sln | 解决方案文件,用 Visual Studio 打开后即可生成 DLL |
| FilterProv.vcxproj | Visual Studio 项目文件,记录工具集版本、平台/配置与项目属性 |
| FilterProv.def | 模块定义文件,声明 DLL 必须导出的函数,供 WDS PXE Server 加载 |
从 FilterProv.vcxproj 可以看到工程的关键属性:
- 配置类型为
DynamicLibrary(即产出 DLL); - 字符集为
Unicode; - 四种构建组合:Debug/Release × Win32/x64,工具集为
v110; - 链接器依赖
wdspxe.lib(WDS PXE 提供程序 API 的导入库),这是编写 PXE Provider 必需的链接项; - 通过
<ModuleDefinitionFile>filterprov.def</ModuleDefinitionFile>指定模块定义文件。
FilterProv.def 的内容极简但关键——它导出唯一的入口函数:
LIBRARY FilterProv EXPORTS PxeProviderInitializeWDSPXE 在加载 Provider DLL 时正是通过查找并调用这个PxeProviderInitialize导出函数来完成初始化的。
核心源码解析:一个过滤器 Provider 的完整实现
DllMain:保存模块实例句柄
FilterProv.cpp 中的DllMain在DLL_PROCESS_ATTACH时把模块实例句柄保存到全局变量g_hInstance,供后续构造配置文件路径时使用(GetModuleFileName需要这个句柄)。
PxeProviderInitialize:初始化、回调注册与请求过滤属性
PxeProviderInitialize是 WDSPXE 调用的第一个函数(FilterProv.cpp),其工作分四步:
- 读取策略配置:调用
InitializeConfiguration()构造配置文件路径并校验全局策略; - 注册关闭回调:
PxeRegisterCallback(hProvider, PXE_CALLBACK_SHUTDOWN, PxeProviderShutdown, NULL),当 WDSPXE 需要关闭 Provider 时回调PxeProviderShutdown; - 注册请求处理回调:
PxeRegisterCallback(hProvider, PXE_CALLBACK_RECV_REQUEST, PxeProviderRecvRequest, NULL),注册接收客户端请求的处理函数; - 声明只处理 PXE 类型的请求:调用
PxeProviderSetAttribute(hProvider, PXE_PROV_ATTR_FILTER, &uFilter, sizeof(uFilter)),其中uFilter = PXE_PROV_FILTER_PXE_ONLY。从源码注释看,该属性表示 Provider只接收"合法 DHCP 报文且包含 Option 60 'PXEClient'"的请求,从而避免处理无关的 DHCP 流量。
最后把 Provider 句柄存入全局g_hFilterProvider,供PxeTrace日志输出使用。
PxeProviderRecvRequest:逐请求决策
这是过滤器 Provider 的核心决策函数(FilterProv.cpp)。每次收到 PXE 请求时,WDSPXE 同步调用它,处理流程如下:
- 通过
GetClientMacAddress()从 DHCP 报文中提取客户端 MAC 地址并格式化为字符串; - 用
GetPrivateProfileString()在配置文件的[Devices]段中查找该 MAC; - 若命中,读取对应的设备级策略(
Allow=1/Deny=0),并校验取值合法性(非法值一律按Deny处理); - 若未命中,则使用全局策略
g_uFilterPolicy(来自[Configuration]段的Policy键)进行裁决。
最终决策以PXE_BOOT_ACTION形式写入输出参数pAction,同时把各参数的"忽略"意图写得很清楚:hClientRequest、uPacketLen、pLocalAddress、pRemoteAddress、pContext在本实现中均未使用(UNREFERENCED_PARAMETER),全部决策只依赖报文中的 MAC 与配置文件。
GetClientMacAddress:从 DHCP 报文提取并格式化 MAC
GetClientMacAddress(FilterProv.cpp)把PXE_DHCP_MESSAGE中的硬件地址转换为定长字符串:
- DHCP 报文中的
HardwareAddress字段长度不定(由HardwareAddressLength给出),函数先用MoveMemory把它右对齐放入长度为PXE_DHCP_HWAADR_SIZE的缓冲区,左侧自动补零; - 随后逐字节将高 4 位、低 4 位转成十六进制字符(
0-9、A-F,字母为大写)。
结合 FilterProv.h 中的定义MAX_MAC_ADDR_STRING_LEN = PXE_DHCP_HWAADR_SIZE * 2 + 1,以及 README 中"MAC 字符串必须为 32 个字符"的要求,可以推断PXE_DHCP_HWAADR_SIZE为 16(DHCPchaddr字段大小),16 字节展开为 32 个十六进制字符,外加一个终止符。这也正是配置文件[Devices]段键名必须是 32 字符(12 位 MAC 前补 20 个0)的原因——源码生成的字符串格式必须与配置文件键名精确一致。
InitializeConfiguration:配置文件路径构造与策略校验
InitializeConfiguration(FilterProv.cpp)负责:
- 用
GetModuleFileName取得 DLL 的完整路径; - 用
StringCchCat追加后缀FILTER_CONFIG_FILE_SUFFIX(在 FilterProv.h 中定义为L".filter.ini"),从而得到配置文件filterprov.dll.filter.ini——配置文件永远与 DLL 同目录、以" DLL 名 + .filter.ini"命名; - 用
GetFileAttributes确认配置文件存在,不存在则报错; - 读取
[Configuration]段的Policy键,将其解析为整数并校验(只接受 0 或 1,其余值视为数据无效); - 通过
PxeTrace(g_hFilterProvider, PXE_TRACE_INFO, ...)输出日志,策略为Deny时记录"Filter Policy: Exclude",为Allow时记录"Filter Policy: Include"。
决策逻辑对照表
结合 README 的语义描述与源码映射,完整决策逻辑如下:
| 客户端状态 | 配置取值 | 源码返回动作(FilterProv.cpp) | 效果(README.txt 描述) |
|---|---|---|---|
MAC 命中[Devices] | 1(Allow) | PXE_BA_REJECTED | 放行:请求被转发给有序列表中的下一个 Provider |
MAC 命中[Devices] | 0(Deny) | PXE_BA_IGNORE | 拒绝:请求不被应答,也不转发给下一个 Provider |
| MAC 未命中 | Policy=1(Allow) | PXE_BA_REJECTED | 放行:未知设备被允许继续流向下一个 Provider |
| MAC 未命中 | Policy=0(Deny) | PXE_BA_IGNORE | 拒绝:未知设备被拒绝 PXE 服务,请求就此终止 |
需要说明的是:README.txt 站在管理员意图角度描述 Deny/Allow 的最终效果,而 FilterProv.cpp 将"允许"映射为返回PXE_BA_REJECTED、将"拒绝"映射为返回PXE_BA_IGNORE;PXE_BOOT_ACTION枚举值由 WDSPXE 在运行时解释,若需要精确的行为语义,请以 WDS PXE API 文档为准。
注册 Provider:让 WDSPXE 认识你的 DLL
根据 cpp/README.txt 的说明,注册一个 Provider 需要两个步骤:
第一步:创建代表你的 Provider 的注册表键
- 在以下路径下创建一个新键(键名自定,例如
FilterProvider):
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSPXE\Providers- 在该新键下创建一个值,指定 Provider DLL 的路径:
| 属性 | 取值 |
|---|---|
| 名称(Name) | ProviderDLL |
| 类型(Type) | REG_EXPAND_SZ |
| 值(Value) | 自定义 Provider DLL 的完整路径与文件名 |
第二步:指定 Provider 在有序列表中的位置
- 修改父键:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSPXE\Providers- 在该键下创建一个多字符串值:
| 属性 | 取值 |
|---|---|
| 名称(Name) | ProvidersOrder |
| 类型(Type) | REG_MULTI_SZ |
| 值(Value) | 第一步中创建的键名(如FilterProvider) |
WDSPXE 会按照ProvidersOrder中列出的顺序把收到的 PXE 请求依次交给注册的 Provider;想让自己的 Provider 永远拥有应答请求的第一机会,就把它排在列表最前面。以管理员身份在 WDS 服务器上执行的等效命令示例如下(依据 README 描述的注册表结构整理):
reg add "HKLM\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSPXE\Providers\FilterProvider" /v ProviderDLL /t REG_EXPAND_SZ /d "C:\WDS\filterprov.dll" /f reg add "HKLM\SYSTEM\CurrentControlSet\Services\WDSServer\Providers\WDSPXE\Providers" /v ProvidersOrder /t REG_MULTI_SZ /d "FilterProvider" /f配置文件 filterprov.dll.filter.ini 详解
命名与位置规则
编译产物必须是 DLL 形式(例如filterprov.dll),配置文件必须命名为filterprov.dll.filter.ini且与 DLL 放在同一目录。命名规则由源码中的FILTER_CONFIG_FILE_SUFFIX(L".filter.ini")强制保证。
[Configuration] 段:Policy 键
该段只有一个键Policy,决定未知设备(不在[Devices]段中的设备)是否继续流向有序列表中的下一个 Provider:
| 取值 | 语义 | 效果 |
|---|---|---|
0 | Deny(拒绝) | 所有未知设备被样例 Provider 拒绝 PXE 服务:请求不被应答,也不转发给有序列表中的下一个 PXE Provider |
1 | Allow(允许) | 所有未知设备被放行:请求被转发给有序列表中的下一个 PXE Provider |
[Devices] 段:按 MAC 的设备级策略
该段列出所有已知设备:
- 键名:设备的 MAC 地址字符串,长度必须为32 个字符(12 位十六进制 MAC 地址前补 20 个
0); - 值:0 或 1,含义与全局
Policy一致——0=Deny(拒绝请求且不转发给下一个 Provider)、1=Allow(把请求转发给有序列表中的下一个 Provider,由下游 Provider 决定是否提供服务)。
由于源码在转换 MAC 时使用大写A-F,配置文件中 MAC 的字母部分应使用大写以保证精确匹配。官方文档示例中的 MAC 位数略有出入,实际配置时应填写完整的 12 位十六进制 MAC,使键名严格保持 32 字符。
完整配置示例一:仅放行指定设备(白名单模式)
[Configuration] Policy=0 [Devices] 00000000000000000000FEFEDEBCBA=1 00000000000000000000123456789AB=1Policy=0意味着默认拒绝所有未知设备;[Devices]中值为1的两台设备被放行。净效果是:只有列出的设备能到达下一个 Provider 并获得 PXE 服务。
完整配置示例二:仅拒绝指定设备(黑名单模式)
[Configuration] Policy=1 [Devices] 00000000000000000000FEFEDEBCBA=0 00000000000000000000123456789AB=0Policy=1意味着默认放行所有未知设备;[Devices]中值为0的两台设备被拒绝。净效果是:除列出的设备外,其余设备都流向下一个 Provider,列出的设备被拒绝 PXE 服务。
端到端运行流程(Walkthrough)
使用该样例过滤器 Provider 的完整操作步骤如下:
- 把样例代码编译成 DLL(如
filterprov.dll); - 按上述格式创建 .ini 配置文件,命名为
filterprov.dll.filter.ini并放在与 DLL 相同的目录; - 将样例 Provider 注册为有序 Provider 列表中的第一个;
- 用 PXE 启动一台客户端机器。此时决策链路为:
- a. PXE Server 把请求交给有序列表中的第一个 Provider(按第 3 步,即本样例 Provider);
- b. 样例 Provider 在文件
[Devices]段中查找引导中的设备:若命中,则遵循该设备指定的策略(Allow/Deny); - c. 若未命中,则读取
[Configuration]段的Policy值并遵循之; - d. 任何没有被样例 Provider 过滤掉的请求,都会被传递给有序列表中的下一个已注册 Provider。
环境要求、构建与运行
系统要求
| 角色 | 要求 |
|---|---|
| 客户端 | 无受支持的系统(本样例面向服务器端开发) |
| 服务器 | Windows Server 2012 R2 |
准备 WDS 服务器
除安装服务器角色外,完成本样例还需要:
- 在 Windows Server 2012 R2 上安装 Windows Deployment Services 服务器角色;
- 初始化 WDS 服务器;
- 向服务器添加引导映像(Boot.wim)。Boot.wim 是一份 Windows PE 映像,客户端机器可引导进入其中以安装操作系统,该文件位于 Windows 产品 DVD 上。
构建
- 启动 Visual Studio,选择File > Open > Project/Solution;
- 进入样例目录,双击打开
FilterProv.sln; - 按F7(Visual Studio 2013 为 F6)或使用Build > Build Solution构建样例,产出
filterprov.dll(Debug/Release、Win32/x64 四种组合均可,工程已通过wdspxe.lib链接 WDS PXE API)。
运行与调试
README 中 F5(Debug > Start Debugging)与 Ctrl+F5(Debug > Start Without Debugging)是 Visual Studio 调试运行的一般流程;就本样例而言,产物是供 WDSPXE 加载的 DLL,并没有独立可执行入口,实际"运行"是将其注册到ProviderDLL指向的路径后,由 WDS PXE Server 进程加载,再通过 PXE 客户端触发请求来验证过滤效果。更详细的运行说明请参阅随样例附带的 cpp/README.txt。
小结
Windows-classic-samples 中的 FilterProvider 样例完整演示了一条自定义 WDS PXE Provider 的研发链路:从 FilterProv.def 导出PxeProviderInitialize,到 FilterProv.cpp 中实现初始化、回调注册、请求裁决与配置加载,再到注册表ProvidersOrder排序与.filter.ini文本配置。理解这个样例后,你可以在此基础上扩展更复杂的过滤逻辑(例如结合数据库、AD 查询或策略服务),在完全复用 WDS PXE Server 核心网络能力的前提下,构建属于自己企业的 PXE 准入与分发方案。
- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
相关推荐
Windows-classic-samples EAPHost 客户端方法示例深度解析:从 DLL 注册到认证状态机的完整实现
Windows classic samples EAPHost 客户端方法示例深度解析:从 DLL 注册到认证状态机的完整实现 本文基于 EapHostClie
示例工程Windows-classic-samples深度解析:从DirectX到UIAutomation的完整API参考
Windows classic samples深度解析:从DirectX到UIAutomation的完整API参考 Windows经典桌面应用程序开发从未如此简
示例工程Windows-classic-samples 实战:通过 InitialSessionState 向 Runspace 注册自定义 Cmdlet(Runspace10 示例解析)
Windows classic samples 实战:通过 InitialSessionState 向 Runspace 注册自定义 Cmdlet(Runspa
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考