Author Posts

March 5, 2017 at 6:41 pm

Although I'm not new to PowerShell, I am new to working with modules. In creating my first module, I am trying to get tab completion to work with the properties contained in one of the functions.

Example: Get-MyFunction | Select-Object MyOb[tab key] should result in Get-MyFunctin | Select-Object MyObjectProperty

In the function, I am creating an array to hold data from the pscustomobject. It is the array that is returned by the function.

[System.Collections.ArrayList]$arrMyArray = @()

Next, I loop through the relevant information, adding them to a pscustomobject:

$MyObject = [PSCustomObject]@{
			MyObjectProperty = $strValue1
			MyOtherObjectProperty = $strValue2

It is these properties that I want to enable tab completion.

At the end of each loop, I add the information to the array object:

$arrMyArray.Add($MyObject) | Out-Null

Once all loops are completed, I return the the array:

Return $arrMyArray

Following the information in this post, I was able to get it to work, but even adding this code within my function will cause the properties to appear in the tab completion of all functions in the module that have an OutputType of 'array'.

Any help or suggestions will be greatly appreciated.

March 5, 2017 at 7:06 pm

You'll need to roll your own class to get tab completion on properties.

Example (PowerShell v5 and later)

class ForumItemData {
    [String] $Name
    [String] $Status
}

function Get-ItemData {

    [CmdletBinding()]
    [OutputType('ForumItemData')]
    param ()

    $obj = [ForumItemData]::new()
    $obj.Name = 'Testing 123'
    $obj.Status = 'Active'
    $obj
}

Get-ItemData | Select-Object -Property Name, Status

Example (PowerShell v4 and earlier)

Add-Type -TypeDefinition @'
using System;

namespace PowerShellOrg.Forum
{
    public class ItemData
    {
        public string Name { get; set; }
        public string Status { get; set; }
    }
}
'@ -ErrorAction SilentlyContinue

function Get-ItemData {

    [CmdletBinding()]
    [OutputType('PowerShellOrg.Forum.ItemData')]
    param ()

    $obj = New-Object -TypeName 'PowerShellOrg.Forum.ItemData'
    $obj.Name = 'Testing 123'
    $obj.Status = 'Active'
    $obj
}

Get-ItemData | Select-Object -Property Name, Status

March 5, 2017 at 7:09 pm

This is hard to do in a script.

.NET uses Reflection to figure out what members a type has; in your case, your type is a PSCustomObject, which does not have any members. At least, none defined in its base, which is all .NET can use.

For one, you're building your function very contrary to PowerShell patterns in terms of accumulating objects in an array. This blocks the pipeline, which is a Bad Thing. See https://devopscollective.gitbooks.io/the-big-book-of-powershell-gotchas/content/manuscript/accumulating-output-in-a-function.html. The Return keyword is misleading, and in a function you probably should avoid it. In a function, it's an alias to Write-Output, which is preferred. Continually adding objects to an array is also a decent performance hit as the array grows.

What you'd need to do, in order for this to work correctly, is register a type (e.g., Add-Type) that has the members you want, and then output objects of that type, and then modify your function declaration to indicate it was outputting objects of that type. It's honestly a lot of work, and it's kind of overkill in terms of a "script cmdlet." You should also be outputting objects one at a time to the pipeline so that PowerShell can stream objects instead of dealing with giant collections.

March 6, 2017 at 1:16 pm

Daniel,

Thanks for the quick and thorough response. I will definitely incorporate this into my script.

March 6, 2017 at 1:18 pm

Thanks Don. It appears I have a lot more homework to do. Thanks for the feedback and tips.