$script:ProviderItem = [System.Management.Automation.CompletionResultType]::ProviderItem
function Get-Files {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)
Get-Files | Where-Object {$_.Name -like "*$WordToComplete*"} | ForEach-Object {
$resultName = $_.Name
$resultFN = $_.FullName
$toolTip = "File: $resultFN"
[System.Management.Automation.CompletionResult]::new($resultName, $resultFN, $script:ProviderItem, $toolTip)
} #ForEach-Object
})]
[System.String[]]$Name
)
begin {Write-Output $script:ProviderItem}
process { foreach ($n in $Name) {Write-Output $n} }
}
'ProviderItem'
instead of a constant in the [System.Management.Automation.CompletionResult]
constructor.Get-Files
function is intended to be a private (non-exported) function.$ProviderItem
is scoped as $global:ProviderItem
but not $script:ProviderItem
$ProviderItem
is scoped globally I still have to export all functions, rather than just Test
, in order to get tab-completion to work properly.
FunctionsToExport = 'Test'
TabExpansion2
and lists child items in the current directory.FunctionsToExport = '*'
Get-Files
inside an ArgumentCompleter
block of a different function's parameter block(s), export only Test
and still retain tab-completion?ArgumentCompleter
function param blocks?Because the ArgumentCompleter
scriptblock has no knowledge about the Module it is being invoked in, thus has no knowledge about variables defined in the module scope. A simple way to prove this is the case is by changing the CompletionResult
arguments to:
[System.Management.Automation.CompletionResult]::new(
$resultName,
$resultFN,
(& (Get-Command Test).Module { $ProviderItem }),
$toolTip)
Moreover, defining the variable as $script:
is not needed, all variables defined in the .psm1
are already scoped to the commands in your module.
Exactly the same applies for Get-Files
if FunctionsToExport = 'Test'
, then the it is scoped to your module and the completer scriptblock has no knowledge about it, you would've to:
& (Get-Command Test).Module { Get-Files } | Where-Object { ....
A workaround can be to use a class that implements IArgumentCompleter
attribute, classes defined in the module scope can see the scoped variables and functions without issues, same applies to Register-ArgumentCompleter
.
Sharing the class implementation here:
using namespace System
using namespace System.Collections
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
$ProviderItem = [CompletionResultType]::ProviderItem
function Get-Files {
Get-ChildItem -Path 'C:\Windows\System32\WindowsPowerShell\v1.0\en-US\about_Functions*.txt'
}
class CustomCompleter : IArgumentCompleter {
[IEnumerable[CompletionResult]] CompleteArgument(
[string] $commandName,
[string] $parameterName,
[string] $wordToComplete,
[CommandAst] $commandAst,
[IDictionary] $fakeBoundParameters
) {
$out = [List[CompletionResult]]::new()
Get-Files | Where-Object { $_.Name -like "*$WordToComplete*" } | ForEach-Object {
$resultName = $_.Name
$resultFN = $_.FullName
$toolTip = "File: $resultFN"
$out.Add([CompletionResult]::new($resultName, $resultFN, $ProviderItem, $toolTip))
}
return $out.ToArray()
}
}
function Test {
[CmdletBinding()]
param (
[Parameter(Mandatory, ParameterSetName = 'Name', Position = 0, ValueFromPipeline)]
[ArgumentCompleter([CustomCompleter])]
[string[]] $Name
)
begin {
Write-Output $ProviderItem
}
process {
foreach ($n in $Name) {
Write-Output $n
}
}
}