PowerShell模块脚本作用域变量在ArgumentCompleter中无法访问的问题咨询
疑惑点:
- 将
$ProviderItem设为$global:ProviderItem时自动补全可用,但设为$script:ProviderItem时不可用 - 即使将
$ProviderItem设为全局作用域,模块清单中仍需导出所有函数而非仅Test,才能使自动补全正常工作:- 无效配置:
FunctionsToExport = 'Test'- 自动补全会回退到
TabExpansion2,列出当前目录的子项。
- 自动补全会回退到
- 有效配置:
FunctionsToExport = '*'- 自动补全可按预期工作。
- 无效配置:
- 原以为这与作用域和PSReadLine有关,但ISE中行为一致,显然忽略了某些关键要点。
问题:
- 如何在仅导出
Test函数的情况下,让其参数块的ArgumentCompleter调用私有函数Get-Files并保留自动补全? - 能否避免使用全局作用域,供
ArgumentCompleter使用模块级常量?
解决方案:
1. 仅导出Test时调用私有Get-Files的实现
核心是让ArgumentCompleter的脚本块能访问模块的私有作用域,有两种可靠方式:
方式一:通过模块实例调用私有函数
在补全脚本块中,利用$MyInvocation.MyCommand.Module获取当前模块对象,再通过Invoke()方法执行私有函数:
function Test { [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0, ValueFromPipeline)] [ArgumentCompleter({ param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) # 获取当前模块实例 $module = $MyInvocation.MyCommand.Module # 调用模块内的私有Get-Files函数 $files = $module.Invoke({ Get-Files }) $files | Where-Object {$_.Name -like "*$WordToComplete*"} | ForEach-Object { $resultName = $_.Name $resultFN = $_.FullName $toolTip = "File: $resultFN" [System.Management.Automation.CompletionResult]::new($resultName, $resultFN, [System.Management.Automation.CompletionResultType]::ProviderItem, $toolTip) } })] [System.String[]]$Name ) begin {Write-Output $script:ProviderItem} process { foreach ($n in $Name) {Write-Output $n} } }
此时模块清单只需配置FunctionsToExport = 'Test',自动补全可正常工作。
方式二:将私有逻辑定义为模块级脚本块
把Get-Files的逻辑改为模块级脚本块变量,补全脚本块直接调用该脚本块:
# 模块级私有脚本块 $script:GetFiles = { Get-ChildItem -Path 'C:\Windows\System32\WindowsPowerShell\v1.0\en-US\about_Functions*.txt' } function Test { [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0, ValueFromPipeline)] [ArgumentCompleter({ param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) # 调用模块级脚本块 & $script:GetFiles | Where-Object {$_.Name -like "*$WordToComplete*"} | ForEach-Object { $resultName = $_.Name $resultFN = $_.FullName $toolTip = "File: $resultFN" [System.Management.Automation.CompletionResult]::new($resultName, $resultFN, [System.Management.Automation.CompletionResultType]::ProviderItem, $toolTip) } })] [System.String[]]$Name ) begin {Write-Output $script:ProviderItem} process { foreach ($n in $Name) {Write-Output $n} } }
同样只需导出Test函数即可,模块作用域的脚本块可被补全逻辑访问。
2. 避免全局作用域使用模块级常量
完全可以不依赖全局作用域,有两种方案:
方案一:直接引用枚举值
无需预先定义模块级变量,在补全逻辑中直接使用枚举的完整类型:
[System.Management.Automation.CompletionResult]::new($resultName, $resultFN, [System.Management.Automation.CompletionResultType]::ProviderItem, $toolTip)
这种方式最简洁,彻底避免作用域问题。
方案二:通过模块实例获取模块级变量
如果需要复用模块级常量,可通过模块会话状态获取:
$script:ProviderItem = [System.Management.Automation.CompletionResultType]::ProviderItem function Test { [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0, ValueFromPipeline)] [ArgumentCompleter({ param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams) $module = $MyInvocation.MyCommand.Module # 从模块会话状态获取私有变量 $providerItem = $module.SessionState.PSVariable.GetValue('ProviderItem') Get-Files | Where-Object {$_.Name -like "*$WordToComplete*"} | ForEach-Object { $resultName = $_.Name $resultFN = $_.FullName $toolTip = "File: $resultFN" [System.Management.Automation.CompletionResult]::new($resultName, $resultFN, $providerItem, $toolTip) } })] [System.String[]]$Name ) begin {Write-Output $script:ProviderItem} process { foreach ($n in $Name) {Write-Output $n} } }
这种方式既保留了模块级常量的复用性,又无需全局作用域,且仅导出Test即可正常工作。
内容的提问来源于stack exchange,提问作者Paul π
相关产品推荐
相关产品推荐

