- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
本指南以 Windows-classic-samples 仓库中的 AccessDBProviderSample03 为骨架,系统讲解如何用 C# 实现一个把 Access 数据库(.mdb)暴露为 PowerShell 驱动器(Drive)的提供程序(Provider)。读完本文,你将掌握ItemCmdletProvider基类的核心方法(NewDrive、GetItem、SetItem、ItemExists、IsValidPath)的编写方式,理解如何通过 ODBC 把数据库表与行映射为类似文件系统的路径结构,并能在本机用Import-Module加载 DLL 后用Get-PSProvider、Get-PSDrive、Get-Item、Set-Item等内建 cmdlet 直接操作数据库内容。
一、样本定位:提供程序系列中的“Item 操作”范例
PowerShell 提供程序的核心理念,是让用户以“文件系统驱动器”这一统一、一致的格式来访问异构数据源。文件系统之外的数据(数据库表、注册表项、环境变量等)只要实现了 Provider,用户就可以用同一套Get-Item、Set-Item、New-Item等 cmdlet 来读写。
在仓库中,PowerShell Provider 样本是一个连续编号的系列,共包含六个目录:
- AccessDBProvider01
- AccessDBProvider02
- AccessDBProvider03
- AccessDBProvider04
- AccessDBProvider05
- AccessDBProvider06
本样本是系列中的第三份(Sample 03)。正如其 README 所述,它演示了三项核心目标:
- 创建基本的 Windows PowerShell 提供程序;
- 创建 PowerShell 驱动器(PowerShell drives);
- 通过提供程序操作项目(items,即数据库中的表与行)。
后续样本(04~06)则在此基础上继续演示额外的 Provider 功能,因此本样本最适合作为理解“ItemCmdletProvider + 驱动器状态管理”这一组合的入门范本。
二、样本目标与实现概览
从源码结构看,本样本把“数据库”抽象成了三级路径空间:
驱动器根(数据库)→ 表名(Table)→ 行号(Row)- 驱动器根即整个 .mdb 数据库文件;
- 一级路径段是表名;
- 二级路径段是行号(从 0 开始的整数索引)。
这一设计直接体现在源码中的PathType枚举与路径解析方法上(见下文第四部分)。
样本由一个主源文件实现全部逻辑:AccessDBProviderSample03.cs,其代码组织为三个区域:
AccessDBProvider类:继承自System.Management.Automation.Provider.ItemCmdletProvider,含驱动器操作(Drive Manipulation)、Item 方法(Item Methods)与辅助方法(Helper Methods);- 三个辅助类:
AccessDBPSDriveInfo(保存驱动器状态)、DatabaseTableInfo(表信息,类比DirectoryInfo)、DatabaseRowInfo(行信息,类比FileInfo)。
从这一对应关系可以推断出作者的抽象思路:表 ≈ 目录,行 ≈ 文件,而DatabaseTableInfo、DatabaseRowInfo的注释也正是这样说明的——前者“Similar to the DirectoryInfo class”,后者“Analogous to the FileInfo class”。
三、驱动器管理:NewDrive 与 RemoveDrive
PowerShell 驱动器由用户通过New-PSDrive -PSProvider AccessDB -Root <数据库文件路径>创建。当 PowerShell 引擎要求提供程序“接管”一个新驱动器时,会调用NewDrive方法。
3.1 NewDrive:建立 ODBC 连接并保存状态
在 NewDrive 实现 中,核心逻辑分四步:
protected override PSDriveInfo NewDrive(PSDriveInfo drive) { // 1. drive 对象不能为 null,否则写入 "NullDrive" 错误 if (drive == null) { ... return null; } // 2. drive.Root 不能为空,且必须指向一个真实存在的文件 if (String.IsNullOrEmpty(drive.Root) || (File.Exists(drive.Root) == false)) { ... return null; } // 3. 构造 ODBC 连接字符串并打开连接 AccessDBPSDriveInfo accessDBPSDriveInfo = new AccessDBPSDriveInfo(drive); OdbcConnectionStringBuilder builder = new OdbcConnectionStringBuilder(); builder.Driver = "Microsoft Access Driver (*.mdb)"; builder.Add("DBQ", drive.Root); OdbcConnection conn = new OdbcConnection(builder.ConnectionString); conn.Open(); // 4. 把连接保存在驱动器对象里返回 accessDBPSDriveInfo.Connection = conn; return accessDBPSDriveInfo; }要点:
- Root 校验:
drive.Root直接来自New-PSDrive -Root参数,必须非空且File.Exists为真,否则通过WriteError写入NoRoot/NullDrive错误记录并返回null(表示创建失败); - 连接字符串:使用
OdbcConnectionStringBuilder生成,指定驱动为Microsoft Access Driver (*.mdb)、数据库文件为DBQ=<Root>; - 状态保存:连接对象被存入自定义的
AccessDBPSDriveInfo(继承自PSDriveInfo)并返回给 PowerShell 引擎。此后该驱动器存续期间,所有 Item 操作都能通过this.PSDriveInfo拿到这条连接——这正是“把与驱动器关联的状态放在这里”的典型做法。
3.2 RemoveDrive:关闭连接
当驱动器被移除(如Remove-PSDrive)时,引擎调用RemoveDrive。实现将传入的PSDriveInfo转回AccessDBPSDriveInfo,若成功则调用Connection.Close()释放 ODBC 连接,防止资源泄漏。
从源码结构看,
AccessDBPSDriveInfo.Connection是普通属性(get/set),因此连接的生命周期完全由 Provider 自身管理:创建时打开,移除时关闭。
四、Item 操作:GetItem、SetItem、ItemExists、IsValidPath
本样本的类声明为:
[CmdletProvider("AccessDB", ProviderCapabilities.None)] public class AccessDBProvider : ItemCmdletProviderCmdletProvider特性注册了提供程序名称AccessDB(对应Get-PSProvider中显示的名字);继承ItemCmdletProvider意味着实现了“单项”操作能力,需要重写以下方法。
4.1 GetItem:按路径取表或行
GetItem 先判断路径是否就是驱动器本身(PathIsDrive),若是则输出驱动器对象;否则用GetNamesFromPath把路径解析为“表名 + 行号”,并按类型分发:
- 路径代表表(
PathType.Table)→ 调用GetTable(tableName),用WriteItemObject(table, path, true)输出(第三个参数isContainer为true,因为表可包含行); - 路径代表行(
PathType.Row)→ 调用GetRow(tableName, rowNumber),用WriteItemObject(row, path, false)输出; - 否则抛出终止性异常
ThrowTerminatingInvalidPathException。
4.2 SetItem:更新一行数据
SetItem 演示了“通过 Provider 写数据”的完整链路:
PathType type = GetNamesFromPath(path, out tableName, out rowNumber); if (type != PathType.Row) { /* 仅支持更新行,否则写 SetNotSupported 错误 */ } OdbcDataAdapter da = GetAdapterForTable(tableName); DataSet ds = GetDataSetForTable(da, tableName); DataTable table = GetDataTable(ds, tableName); string[] colValues = (values as string).Split(','); DataRow row = table.Rows[rowNumber]; for (int i = 0; i < colValues.Length; i++) { row[i] = colValues[i]; } if (ShouldProcess(path, "SetItem")) { da.Update(ds, tableName); }关键设计:
- values 约定:
Set-Item传入的values被当作逗号分隔字符串,按列顺序逐列写入DataRow; - 越界保护:
rowNumber >= table.Rows.Count时抛出ArgumentException,提示新增行应使用NewItem(该能力在后续样本中实现); - 写入确认:使用
ShouldProcess(path, "SetItem")包裹da.Update(...),使-Confirm等确认机制生效——这是 PowerShell Provider 实现写操作时的标准做法; - 无手工 SQL 更新语句:更新通过
OdbcDataAdapter+OdbcCommandBuilder自动生成,SetItem本身不需要编写任何 UPDATE SQL。
4.3 ItemExists 与 IsValidPath
ItemExists:对驱动器路径恒返回true;对表路径要求GetTable结果非空;对行路径还要求行号小于table.RowCount。IsValidPath:先做空值检查,再通过NormalizePath把/统一替换为反斜杠,然后用pathSeparator拆分路径段,任何一段为空即视为非法。
这两个方法分别支撑Test-Path与路径合法性校验等内建 cmdlet 语义。
五、路径解析机制:PathType、表名校验与防注入
5.1 路径分段与类型判定
GetNamesFromPath是整套路径语义的核心,其解析规则为:
| 路径段数 | 语义 | 结果 |
|---|---|---|
| 仅驱动器根 | 整个数据库 | PathType.Database |
| 1 段 | 表名(须通过TableNameIsValid) | PathType.Table |
| 2 段 | 表名 + 行号(须能转换为非负整数) | PathType.Row |
| 超过 2 段 | 非法 | 写入PathNotValid错误 |
行号转换使用SafeConvertRowNumber,捕获FormatException(写入RowStringFormatNotValid)与OverflowException(写入RowStringConversionToNumberFailed),失败时返回-1表示无效。
5.2 表名正则:防御 SQL 注入
源码中定义的表名校验正则:
private static string pattern = @"^[a-z]+[0-9]*_*$";TableNameIsValid用该正则(RegexOptions.Compiled | RegexOptions.IgnoreCase)匹配表名,不匹配即写入TableNameNotValid错误。由于表名随后会被拼进"Select count(*) from \"" + tableName + "\""之类的 SQL 语句中,这一白名单式校验是防御 SQL 注入的关键一环——只允许“字母开头、可跟数字和下划线”的标识符。
5.3 其他辅助方法
GetTables/GetRows:枚举数据库全部表(通过connection.GetSchema("Tables"))或指定表的全部行(通过 DataAdapter 填充 DataSet),分别生成DatabaseTableInfo/DatabaseRowInfo集合;枚举行数时执行SELECT count(*),失败则按 0 处理;GetAdapterForTable:校验表名合法且表存在后,创建OdbcDataAdapter并关联OdbcCommandBuilder,使后续 UPDATE 无需手写 SQL;连接未打开时自动Open();GetDataSetForTable/GetDataTable:以DataSet作为数据源的内存表示,并把Locale设为InvariantCulture;PathIsDrive:用path.Replace(this.PSDriveInfo.Root, "")判断路径是否仅剩驱动器根。
六、辅助类:驱动器状态与“表/行”信息对象
三个辅助类都在源码 AccessDBProviderSample03.cs 中定义:
AccessDBPSDriveInfo : PSDriveInfo:唯一新增成员是OdbcConnection Connection属性,承载驱动器生命周期内的数据库连接;DatabaseTableInfo:包含Data(来自 Tables schema 的 DataRow)、Name(表名)、RowCount(行数)、Columns(列定义集合)。语义上对应“目录”;DatabaseRowInfo:包含Data(行的 DataRow)与RowNumber(行索引字符串)。语义上对应“文件”。
这些对象由 Provider 在GetItem等方法的WriteItemObject中输出,用户可以在管道中对它们进行后续处理或格式化显示。
七、构建样本
7.1 工程配置
解决方案 AccessDBProviderSample03.sln 为 Visual Studio 2012 格式;工程文件 AccessDBProviderSample03.csproj 的关键配置如下:
OutputType为Library——本样本编译结果是DLL(模块),而非可执行文件;AssemblyName为AccessDBProviderSample03,对应运行步骤中的Import-Module .\AccessDBProviderSample03.dll;TargetFrameworkVersion为v4.0;- 引用了
System.Management.Automation(Version 3.0.0.0,通过 GAC 路径引入)、System.Data(ODBC/DataSet 所需)、System.Xml等程序集; - Debug 配置输出到
.\bin\Debug\,Release 配置输出到.\bin\Release\,与 README 中“库将构建在默认 \bin 或 \bin\Debug 目录”的描述一致。
7.2 构建步骤(Visual Studio)
- 启动 Visual Studio,选择File→Open→Project/Solution;
- 进入样本目录 Samples/PowerShell/Provider/AccessDBProvider03/cs,双击解决方案文件
AccessDBProviderSample03.sln; - 按F7或通过Build→Build Solution构建;
- 产物出现在
bin\Debug\AccessDBProviderSample03.dll(或 Release 目录)。
八、运行样本
按 README 给出的步骤,在 Windows PowerShell 中操作:
- 启动 Windows PowerShell;
- 切换到包含样本 DLL 的目录(如
bin\Debug); - 加载模块:
Import-Module .\AccessDBProviderSample03.dll- 查看新出现的提供程序:
Get-PSProvider应能看到名为AccessDB的提供程序条目。
- 创建并查看驱动器。结合源码中
NewDrive的校验规则,-Root必须是指向真实存在的 .mdb 文件的路径:
New-PSDrive -Name mydb -PSProvider AccessDB -Root C:\samples\test.mdb Get-PSDrive mydbNew-PSDrive会触发NewDrive,进而打开 ODBC 连接并把AccessDBPSDriveInfo挂到该驱动器上。
- 用
Get-Item/Set-Item操作数据。路径语义为驱动器:表名与驱动器:表名\行号:
# 取整张表 Get-Item mydb:\Employees # 取第 2 行(行号从 0 开始) Get-Item mydb:\Employees\1 # 更新第 2 行的各列值(逗号分隔字符串) Set-Item mydb:\Employees\1 -Value "张三,IT,10000" # 验证是否存在 Test-Path mydb:\Employees需要说明的前提与限制:
- README 声明的操作系统要求为客户端Windows 8.1、服务器Windows Server 2012 R2(并面向 .NET Framework 4.0 / System.Management.Automation 3.0);
- 数据库访问依赖本机安装的
Microsoft Access Driver (*.mdb)ODBC 驱动,因此需要相应 Access/ODBC 组件可用; - 表名必须满足源码正则
^[a-z]+[0-9]*_*$,Set-Item仅支持更新已存在的行,新增行需使用NewItem(本样本未实现,属后续样本范畴)。
九、与系列样本的关系及延伸学习
本样本只实现了 Item 层面的操作(ItemCmdletProvider)。同一仓库中还有 AccessDBProvider01、AccessDBProvider02、AccessDBProvider04、AccessDBProvider05、AccessDBProvider06 等后续样本,用于演示更多 Provider 功能(如导航、内容、子项目等能力)。想系统学习 PowerShell Provider 的完整体系,可以按编号顺序通读该系列;想快速把握“驱动器 + Item 操作”这一最小闭环,本样本(Sample 03)是首选的精读对象。
- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
相关推荐
Windows 驱动示例解析:基于 KMDF 的 WMI 数据提供程序(WmiSamp)完整实现指南
Windows 驱动示例解析:基于 KMDF 的 WMI 数据提供程序(WmiSamp)完整实现指南 本文以 Windows driver samples 仓库
示例工程YARP动作转换提供程序:ActionTransformProvider实现
YARP动作转换提供程序:ActionTransformProvider实现 概述 在YARP(Yet Another Reverse Proxy)反向代理框架
后端API网关网络基于证据驱动为 AI Agent 编写 Trellis Spec:以 EcoPaste 仓库为范例的实操指南
基于证据驱动为 AI Agent 编写 Trellis Spec:以 EcoPaste 仓库为范例的实操指南 本文以 EcoPaste 仓库中的 spec wr
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考