AWS Developer Tools Blog

Argument Completion Support in AWS Tools for Windows PowerShell

Version 3.1.93.0 of the AWS Tools for Windows PowerShell now includes support for tab completion of parameters that map to enumeration types in service APIs. Let’s look at how these types are implemented in the underlying AWS SDK for .NET and then see how this new support helps you at the Windows PowerShell command line or in script editors (like the PowerShell ISE) that support parameter IntelliSense.

Enumerations in the AWS SDK for .NET

You might expect the SDK to implement enumeration types used in service APIs as enum types but this isn’t the case. The SDK contains a ConstantClass base class from which it derives classes for service-specific enumeration types. These derived classes implement the permitted values for a service enumeration as a set of read-only static strings. For example, here’s a snippet of the InstanceType enumeration for Amazon Elastic Compute Cloud (Amazon EC2) instances (comments removed for brevity):

public class InstanceType : ConstantClass
{
    public static readonly InstanceType C1Medium = new InstanceType("c1.medium");
    public static readonly InstanceType C1Xlarge = new InstanceType("c1.xlarge");
    public static readonly InstanceType C32xlarge = new InstanceType("c3.2xlarge");
	...

    public InstanceType(string value)
           : base(value)
    {
    }
	...
}

In a typical SDK application, you would use the defined types (from Amazon EC2’s RunInstances API), like this:

var request = new RunInstancesRequest
{
    InstanceType = InstanceType.C1XLarge,
	...
};
var response = EC2Client.RunInstances(request);
...

In this way, the SDK’s enumerations are not very different from regular enum types but offer one very powerful capability over regular enums: When services update their enumeration values (for example when EC2 adds a new instance type) you do not need to update the version of the SDK your application is built against to use the new value! The new value won’t appear as a member of the enumeration class until you update your SDK but you can simply write code to use the value with whatever version you have. It just works:

var request = new RunInstancesRequest
{
    InstanceType = "new-instance-type-code"
	...
};
var response = EC2Client.RunInstances(request);
...

This ability to adopt new values also applies to the response data from the service. The SDK will simply unmarshal the response data and accept the new value. This is unlike what would happen with the use of real enum types that would throw an error. You are therefore insulated on both sides from services adding new enumeration values when you want or need to remain at a particular SDK version.

Using Service Enumerations from PowerShell

Let’s say we are working at a console and want to use New-EC2Instance (which maps to the RunInstances API):

PS C:> New-EC2Instance -InstanceType ???

As we noted, the underlying SDK does not use regular .NET enum types for the allowed values so there’s no data for the shell to run against in order to offer a suggestion list. Obviously this is a problem when you don’t know the permitted values but it’s also an issue when you know the value but not the casing. Windows PowerShell may be case-insensitive but some services require the casing shown in their enumerations for their API call to succeed.

Why Not Use ValidateSet?

One way to tackle this problem would be to use PowerShell’s ValidateSet attribution on parameters that map to service enumerations, but this has a shortcoming: validation! Using the example of the -InstanceType parameter again, should EC2 add a new type you would need to update your AWSPowerShell module in order to use the new value. Otherwise, the shell would reject your use of values not included in the ValidateSet attribution at the time we shipped the module. The ability to make use of new enumeration values without being forced to recompile the application with an updated SDK is very useful. It’s certainly a capability we wanted to extend to our Windows PowerShell module users.

What we want is behavior similar to these screenshots of Invoke-WebRequest but without locking users into requiring updates for new values. In the ISE, we get a pop-up menu of completions:

At the console when we press Ctrl+Space we get a list of completions to select from:

We can also use the Tab key to iterate through the completions one by one or we can enter a partial match and the resulting completions are filtered accordingly.

The Solution: Argument Completers

Support for custom argument completers was added in Windows PowerShell version 3. Custom argument completers allow cmdlet authors and users to register a script block to be called by the shell when a parameter is specified. This script block is responsible for returning a set of valid completions given the data at hand. The set of completions (if any) is displayed to the user in the same way as if the data were specified using ValidateSet attribution or through a regular .NET enum type.

Third-party modules like TabExpansionPlusPlus (formerly TabExpansion++) also contributed to this mechanism to give authors and users a convenient way to register the completers. Beginning in Windows PowerShell version 5, a new native cmdlet can perform the registration.

In version 3.1.93.0 of the AWSPowerShell module we have added a nested script module that implements argument completers across the supported AWS services. The data used by these completers to offer suggestion lists for parameters comes from the SDK enumeration classes at the time we build the module. The SDK’s data is created based on the service models when we build the SDK. The permitted values are therefore correctly cased for those services that are case-sensitive; no more guessing how a value should be expressed when at the command line.

Here’s an example of the InstanceType completer (shortened) for EC2:

$EC2_Completers = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
    
    # to allow for same-name parameters of different ConstantClass-derived
    # types, check on command name concatenated with parameter name.
    switch ($("$commandName/$parameterName"))
	{	...
        # Amazon.EC2.InstanceType
        {
            ($_ -eq "Get-EC2ReservedInstancesOffering/InstanceType") -Or
            ($_ -eq "New-EC2Instance/InstanceType") -Or
            ($_ -eq "Request-EC2SpotInstance/LaunchSpecification_InstanceType")
        }
        {
            $v = "c1.medium","c1.xlarge",...,"t2.nano","t2.small",...
            break
        }
	...
	}
	
    # the standard code pattern for completers is to pipe through sort-object
    # after filtering against $wordToComplete, but our members are already sorted.
    $v |
        Where-Object { $_ -like "$wordToComplete*" } |
        ForEach-Object { New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }
}        

When the AWSPowerShell module is loaded, the nested module is automatically imported and executed, registering all of the completer script blocks it contains. Completion support works with Windows PowerShell versions 3 and later. For Windows PowerShell version 5 or later, the module uses the native Register-ArgumentCompleter cmdlet. For earlier versions it determines if this cmdlet is available in your installed modules (this will be the case if you have TabExpansionPlusPlus installed). If the cmdlet cannot be found the shell’s completer table is updated directly (you’ll find several blog posts on how this is done if you search for Windows PowerShell argument completion).

The net effect of this is that when you are constructing a command at the console or writing a script you get a suggestion list for the values accepted by these parameters. No more hunting through documentation to determine the allowed values and their casing! As we required, the ISE displays the list immediately after you enter a space after the parameter name and partial content will filter the list:

In a console the Tab key will cycle through the available options. Pressing Ctrl+Space displays a pop-up selection list that you can cursor around. In both cases you can filter the display by typing in the partial content:

A Note About Script Security

To accompany the new completion script module we made one other significant change in the 3.1.93.0 release: to add an Authenticode signature to all script and module artifacts (in effect, all .psd1, .psm1, .ps1 and .ps1xml files contained in the module). A side-benefit of this is that the module is now compatible with where the execution policy for Windows PowerShell scripts is set to “AllSigned”. More information on execution policies can be found in this TechNet article.

Wrap

We hope you enjoy the new support for parameter value completion and the ability to now use the module in environments that require the execution policy to be ‘AllSigned’. Happy scripting!