Home > Back-end >  I have a function that returns a PSCustomObject with specific named properties. How can I get these
I have a function that returns a PSCustomObject with specific named properties. How can I get these

Time:08-13

Here is a simple function that splits a file path into its individual components and assigns them to a handful of properties:

function Get-FilePathComponents {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,Position=0,ValueFromPipeline)]
        [String[]]
        $Path
    )

    begin {}

    process {

        foreach ($P in $Path) {
            [PSCustomObject]@{
                ContainingFolder     = Split-Path $P -Parent
                FileBaseName         = Split-Path $P -LeafBase
                FileFullName         = Split-Path $P -Leaf
                FileExtension        = Split-Path $P -Extension
                FullPathNoExtension  = [IO.Path]::Combine((Split-Path $P -Parent),(Split-Path $P -LeafBase))
                CompletePath         = $P
                ParentFolder         = Split-Path (Split-Path $P -Parent) -Parent
            }
        }
    }
}

I then use the function like this:

$DestFile = "C:\Images\Wallpapers\SomeCoolWallpaper.jpg"
$Components = Get-FilePathComponents $DestFile

$Components.FileFullName     # Outputs SomeCoolWallpaper.jpg
$Components.ContainingFolder # Outputs C:\Images\Wallpapers

What I really need to accomplish is to somehow enable autocompletion for the returned object and its important properties.

This image below is exactly what I want:

Autocomplete

I was able to get it to work by defining a custom types.ps1xml file and populating it with <ScriptProperty> members that correspond to my parameters:

<Types>
<Type>
<Name>VSYSFileOps.Object.FilePathComponents</Name>

<ScriptProperty>
   <Name>ContainingFolder</Name>
   <GetScriptBlock>
      $this.ContainingFolder
   </GetScriptBlock>
</ScriptProperty>

<ScriptProperty>
   <Name>FileBaseName</Name>
   <GetScriptBlock>
      $this.FileBaseName
   </GetScriptBlock>
</ScriptProperty>

<ScriptProperty>
   <Name>FileFullName</Name>
   <GetScriptBlock>
      $this.FileFullName
   </GetScriptBlock>
</ScriptProperty>

... Etc...

And then adding [OutputType('VSYSFileOps.Object.FilePathComponents')] to the beginning of my function. Surprisingly, while this seems to work, I've been informed by some very knowledgeable people that this is a very bad idea and not what ps1xml files are used for.

I really want to get auto-completion working for this. How can I make this happen?

Any help at all would be extremely welcomed.

Edit: I am using VSCode with the official PowerShell extension and Powershell Pro Tools.

Edit 2 (Working Solution):


Many thanks to @AdminOfThings. His advice led me to a great solution.

Here's the final working code:

$FilePathComponents = @'
public class FilePathComponents
{
    public string ContainingFolder { get;set; }
    public string FileBaseName { get;set; }
    public string FileFullName { get;set; }
    public string FileExtension { get;set; }
    public string FullPathNoExtension { get;set; }
    public string CompletePath { get;set; }
    public string ParentFolder { get;set; }
}
'@

Add-Type -TypeDefinition $FilePathComponents -Language CSharp

function Get-FilePathComponents {

    [OutputType("FilePathComponents")]

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,Position=0,ValueFromPipeline)]
        [String[]]
        $Path
    )

    begin {}

    process {
        foreach ($P in $Path) {
            $obj = [FilePathComponents]::new()
            $obj.ContainingFolder     = Split-Path $P -Parent
            $obj.FileBaseName         = Split-Path $P -LeafBase
            $obj.FileFullName         = Split-Path $P -Leaf
            $obj.FileExtension        = Split-Path $P -Extension
            $obj.FullPathNoExtension  = [IO.Path]::Combine((Split-Path $P -Parent),(Split-Path $P -LeafBase))
            $obj.CompletePath         = $P
            $obj.ParentFolder         = Split-Path (Split-Path $P -Parent) -Parent
            $obj
        }
    }
}

The key was adding [OutputType("FilePathComponents")] to the beginning of the function definition. Now, when I use the function outside of the module, Intellisense and TAB completion work like a charm, even without strictly typing the variable name:

Autocompletion

CodePudding user response:

If you want to go the custom class route, you can add a custom class as a type definition or by loading the class code in a script file/console:

# class definition
$class = @'
public class FilePathComponents
{
    public string ContainingFolder { get;set; }
    public string FileBaseName { get;set; }
    public string FileFullName { get;set; }
    public string FileExtension { get;set; }
    public string FullPathNoExtension { get;set; }
    public string CompletePath { get;set; }
    public string ParentFolder { get;set; }
}
'@
# add class to PowerShell session
Add-Type -TypeDefinition $class
# define function
function Get-FilePathComponents {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,Position=0,ValueFromPipeline)]
        [String[]]
        $Path
    )

    begin {}

    process {

        foreach ($P in $Path) {
            $obj = [FilePathComponents]::new()
            $obj.ContainingFolder     = Split-Path $P -Parent
            $obj.FileBaseName         = Split-Path $P -LeafBase
            $obj.FileFullName         = Split-Path $P -Leaf
            $obj.FileExtension        = Split-Path $P -Extension
            $obj.FullPathNoExtension  = [IO.Path]::Combine((Split-Path $P -Parent),(Split-Path $P -LeafBase))
            $obj.CompletePath         = $P
            $obj.ParentFolder         = Split-Path (Split-Path $P -Parent) -Parent
            $obj
        }
    }
}

# call your function
$file = Get-FilePathComponents C:\temp\test1\a.csv
# use $file. to auto-populate the properties
  • Related