如何让 PowerShell 的 PSDrive Item 在每次读取时动态求值?
可以实现动态 PSDrive Item,但需要底层 Provider 在读取时计算内容。原代码使用的 Environment Provider 不提供这种计算机制,单靠 New-PSDrive 和 New-Item 无法让 $PS:Now 自动返回当前时间。
为什么原代码中的时间不会更新?
执行下面的代码时,([DateTime]::Now) 会立即求值,结果随后传给 New-Item:
$null = New-Item -Path PS: -Name Now -Value ([DateTime]::Now)
保存下来的是当时的时间值。之后读取 Item 时,不会重新执行创建它时使用的表达式。
PS: 使用 Environment Provider,因此 PS:Now 对应当前进程中的环境变量 Now。环境变量保存的是字符串,传入的 DateTime 对象也会转换为字符串。相关行为见 Environment Provider 文档和环境变量文档。
New-PSDrive 创建访问入口,Item 的读写行为仍由所选 Provider 决定。更换驱动器名称不会改变存储机制。
如果必须保留 $PS:Now,应怎样实现?
可以实现一个支持内容读取的自定义 Provider,并将 PS: 驱动器关联到这个 Provider。
$PS:Now 这种写法可以通过 Provider 的内容接口读取数据。PowerShell 团队的变量语法访问 Provider 示例说明,实现 IContentCmdletProvider 的 Provider 可以使用这种访问方式。
实现时需要处理以下部分:
- 定义 Provider 的命名空间,使
Now成为有效路径。 - 实现
IContentCmdletProvider,让GetContentReader(path)为Now返回一个IContentReader。 - 在 reader 的读取逻辑中计算
DateTime.Now,而不是读取创建 Item 时缓存的时间。 - 每次内容访问返回一个时间值后结束本次读取;下一次访问时创建新的 reader,重新计算时间。
- 将 Provider 编译并加载为模块,再创建关联到它的
PS:驱动器。
接口关系见 IContentCmdletProvider 文档。如果 Provider 只提供只读的计算项,还需要处理写入、清空等操作,例如对不支持的操作返回相应错误。
还需要区分两个读取入口:Get-Item PS:Now 获取 Item,$PS:Now 读取 Item 的内容。只修改 Provider 的 GetItem 实现,不足以实现这里要求的变量语法读取。
这需要开发自定义 Provider,不能直接套用在现有的 Environment Provider 上。自定义 Provider 可以预先暴露 Now,不一定要通过 New-Item 创建它。
如果只是需要每次取得当前时间,可以直接调用函数
如果不要求必须使用 $PS:Now 语法,用函数更简单:
function Get-CurrentTime {
[DateTime]::Now
}
Get-CurrentTime
Start-Sleep -Seconds 1
Get-CurrentTime
也可以将计算逻辑保存在 ScriptBlock 中,每次显式执行:
$Now = { [DateTime]::Now }
& $Now
Start-Sleep -Seconds 1
& $Now
& $Now 会执行 ScriptBlock,单独读取 $Now 则只会得到 ScriptBlock 对象。环境变量只保存字符串,因此将 ScriptBlock 写入环境变量也无法让它自动执行。
断点能解决这个问题吗?
Set-PSBreakpoint 文档列出了行断点、命令断点和变量断点,没有列出通用的 PSDrive Item 读取断点接口。虽然 $PS:Now 看起来像变量,也不能直接套用普通变量的读取断点方案。参见 Set-PSBreakpoint 文档。
对于这个原型,如果要研究动态 Item 本身,应把计算逻辑放在自定义 Provider 的内容读取流程中。如果只需要获取当前时间,直接调用 Get-Date、函数或 ScriptBlock 即可。
备注:内容仅供参考。
