Download the GetDevice PowerShell Module
There is a PDF copy of this entire blog post inside of the attached zip file. It’s much more readable.
The purpose of this document is to describe the goal and solution for creating dynamically-injected parameter auto-completion values into PowerShell function definitions. This is simply a proof of concept, and not a complete solution.
In PowerShell version 3.0, parameters can be configured to auto-complete values that can be passed in. In prior version of the product, auto-completion was limited to PowerShell provider paths (eg. any child node within a PSDrive).
The goal of this proof of concept is to dynamically inject auto-completion values into a PowerShell script function (aka. script cmdlet). As of right now, I do not believe that it is possible to inject auto-completion values into a parameter definition on a function; rather, the auto-completion values must be pre-defined in the parameter’s definition within the function definition.
The proposed solution is to create a function template, which will have values dynamically injected into it when a module is imported.
There are several parts to this solution, which is implemented as a module.
The module file
Imports / calls the “value retriever” script and stores values into an array
Joins the array of values into a string, on a comma
Replaces the placeholder in the template function with the validation string
Writes the resulting function to a file and imports it into the session
<< You are now ready to use the dynamically generated function >>
Retrieves a list of valid values for the desired parameter in the template script
Returns the list of values to the module “installer” (item #1)
Serves as a template function that will have the values substituted into it
Final function definition
A copy of the template function that will contain the substituted values
Will be imported by the module “installer”
Can be called by the end user once
Let us create a sample module, which will contain a single function that retrieves a Windows Management Instrumentation (WMI) device for a computer, based on its friendly name (Caption). We will call this function Get-Device. Normally, we could simply use the Get-CimInstance or Get-WmiObject cmdlets to enumerate the Win32_PnpEntity WMI class, but in order to retrieve a specific device by name, we would have to write a WMI filter, which can be a pain to write if you are not already familiar with them. An alternative would be to filter the entire result set using PowerShell’s Where-Object cmdlet, but that is not very performance-friendly.
Instead, what our Get-Device function will do is use a WMI filter, but the end-user will pass in a value as a function parameter, instead of having to write a custom WMI filter. The WMI filter is pre-defined inside of the function.
Let’s take a look at the example project files, and how to use them in the next couple sections.
Example Project Files
This is the module “installer” file that helps PowerShell import the module into the current session (runspace). The logic to go out and grab the dynamic value list, and replace these values into the template function is implemented inside the module installer.
Defines the Get-Device function, but it not entirely valid PowerShell code. This file is not directly executed directly, but is instead used to generate the “real” Get-Device.ps1 file.
This file is dynamically generated based off of the Get-Device.ps1.template file when the module is imported. It is forcibly overwritten if it already exists by the module “installer” file (GetDevice.psm1).
The Get-Device function is defined in this file, and has a single parameter: DeviceName. This parameter definition will contain all of the values returned by the Get-DeviceNames.ps1 function.
This is a function that retrieves the values that will be injected as auto-completion values into the Get-Device.ps1 file. Currently this function is exported as a function within the GetDevice module, but it is not intended to be called by the end user directly. See the “Known Issues” section for more information.
Using the Example Module
To use the example module, simply extract it to a path on your filesystem, and import the fully qualified path to the GetDevice.psm1 file. You can also extract it to your PowerShell module path and import it by simply using the module’s name (GetDevice).
# If the module is NOT in your $env:PSModulePath, use this
Import-Module -Name c:\extract\GetDevice\GetDevice.psm1;
# If the module is in your module path, use this
Import-Module -Name GetDevice;
Testing Auto-Completion on Get-Device
When you imported the GetDevice module, a function called Get-Device was dynamically generated. You can test out the auto-completion by typing the following:
Get-Device -DeviceName a<tab>
That’s all there is to it!
Quotes around values
Currently, if a parameter value requires quotes around it, they are not automatically added by the PowerShell console. Attempts to add quotes to the valid parameter values caused the values themselves to contain quotes, which meant that the first set of quotes around a value (when the Get-Device function is called) would get ignored, and validation would fail.
Leftover public function definitions
The “value retriever” function is currently exported as a public function when the GetDevice module is imported. This could be prevented in several different ways, but is beyond the scope of this proof of concept. For example, two potential solutions:
You could use “Remove-Item” to remove the function definition using the function: PSdrive
Use a PowerShell manifest file (.psd1) to limit which functions are exported from the module
The “value retriever” function (Get-DeviceNames.ps1) does not return unique values. It returns ALL device names, even if there are duplicates. This could easily be restricted to prevent duplicate values from being returned.
You must type at least one character to use tab-completion
Although the built-in PowerShell parameter auto-completion allows you to simply hit “tab” to start cycling through auto-completion values, when you are writing custom functions using the [ValidateSet()] parameter, you must type at least one character to get auto-completion “started.” There is currently no known work-around or solution to this problem.