PowerShell: Generating functions with dynamic parameter auto-completion values

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.

Introduction

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.

Problem

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).

Goal

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.

Solution

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 >>
  • Value retriever

  • 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)
  • Template function

  • 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
  • Example Solution

    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

    GetDevice.psm1

    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.

    Get-Device.ps1.template

    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.

    Get-Device.ps1

    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.

    Get-DeviceNames.ps1

    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

    Installing

    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&lt;tab&gt;

    That’s all there is to it!

    Known Issues

    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
  • Unique Values

    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.

    7 thoughts on “PowerShell: Generating functions with dynamic parameter auto-completion values

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>