Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
September 2, 2025

SpectreConsole Toolmaking

One of the things I enjoy the most about PowerShell is the ability to easily create tools. Often, I'll realize I want to do something that isn't currently possible or wish there was an easier way to accomplish a task. Often, I'll find myself verbalizing the task I want to accomplish. In so doing, I often recognize what PowerShell command or concept I can use to achieve my goal. This almost always means creating a new PowerShell tool in the form of a function. If I end up with several related functions, I might even create a module.

I'm also the type of person who enjoys the process and is open to a journey. It is not unusual for me to start with one idea and in the process create additional tools. And even I still learn things in the process so it is a win-win. Actually, a triple win because I also get to share what I've learned with you.

The Challenge

My initial challenge centered on selecting files from a folder. I often want to select a group of files and do something with them. The challenge is when the files have different names or extensions which preclude using wild cards. Using a regular expression pattern in Where-Object is possible but tedious. I want to make this as easy to use from a PowerShell prompt as possible.

I could use Out-GridView or Out-ConsoleGridView as an object picker.

dir c:\work\*.ps1 | Out-ConsoleGridView -Title "Select one or more files"

> Install the Microsoft.PowerShell.ConsoleGuiTools module in PowerShell 7 to get this command.

Out-ConsoleGridView as an object picker
figure 1

Selected objects are passed to the pipeline.

    Directory: C:\work

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           3/23/2023  6:23 PM            292 d.ps1
-a---           3/23/2023  6:24 PM          27673 d2.ps1
-a---           1/30/2025  9:08 AM           1570 dev-caesar2.ps1
-a---           1/26/2025  5:16 PM           4029 dev-topproc.ps1

It wouldn't be too difficult to create a function that wraps this command. However, this felt a little clunky. I wanted something more elegant.

I know that the pwshSpectreConsole module has a several list commands. I've written about this module in the past. I was intrigued with the idea of a richer, more colorful experience. I was also looking for an excuse to do more with the module.

Read-SpectreMultiSelection

I knew I wanted to be able to select multiple files which led me to Read-SpectreMultiSelection. After reading the help, I did a quick proof-of-concept test from the PowerShell prompt.

$files = dir c:\work\*.ps1
$paramHash = @{
    allowEmpty          = $True
    Message             = 'Select one or more files'
    Choices             = $files
    ChoiceLabelProperty = 'Name'
    PageSize            = 10
    Color               = "Gold1"
}

Read-SpectreMultiSelection @paramHash
Read-SpectreMultiSelection demonstration
figure 2

> As of version 2.3 of the pwshSpectreConsole module, you can't pipe objects to Read-SpectreMultiSelection. You must use the Choices parameter. I've been told that you will be able to pipe objects to this command in the next release.

Out-SelectFile

Now that I know what I want to do, I can take this code and wrap it into a function. It makes sense to expose Read-SpectreMultiSelection parameters as function parameters, although I will likely set some defaults.

The major decision was to determine how my new function would fit into the PowerShell ecosystem. How would I want to use it? I could design the function to get the files and then present them in the list. That would mean a Path parameter. I'd also need something to filter the files. Or, I could create a function that only wraps the Read-SpectreMultiSelection command. I can let function only process the files passed to it and present them for selection. Getting the files would be up to me. Selecting them from a list with my new command would be a separate step.

This is the direction I chose. This is also more in line with the PowerShell philosophy of functions doing one thing. Getting files is one step. I can then pipe the files to my selection function, which will write selected files to the pipeline. I can then pipe the selected files to another command such as Copy-Item or Remove-Item.

#requires -version 7.5
#requires -module pwshSpectreConsole

Function Out-SelectFile {
    <#
    .SYNOPSIS
    Select files from a SpectreConsole list
    .DESCRIPTION
    Select files using Read-SpectreMultiSelection from the pwshSpectreConsole module. Selected objects will be written to the pipeline.

    The filenames must be unique.
    .EXAMPLE
    PS C:> dir c:\temp -file | Out-SelectFile | Remove-Item
    .LINK
    Read-SpectreMultiSelection
    #>
    [CmdletBinding()]
    [alias('osf')]
    param (
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [System.IO.FileInfo[]]$InputObject,

        [Parameter(HelpMessage = "Specify the select page size greater or equal to 5.")]
        [ValidateScript({$_ -gt 5},ErrorMessage = "Specify a page size >= 5")]
        [int32]$PageSize = 25,

        [Parameter(HelpMessage = "Specify a pwshSpectreConsole color")]
        [alias("Color")]
        [string]$SelectionColor = "Violet",

        [Parameter(HelpMessage = "Specify a timeout value in seconds.")]
        [ValidateScript({$_ -gt 0})]
        [int]$Timeout
    )
    Begin {
        #initialize an empty list to hold the files
        $list = [System.Collections.Generic.List[System.IO.FileInfo]]::new()
    } #begin
    Process {
`       #add each file to the list
        foreach ($file in $InputObject) {
            $list.Add($file)
        }
    } #process
    End {
        if ($list.Count -gt 0) {
            Write-Information $list
            #get the top-most directory path
            $top = $list.DirectoryName | Sort-Object Length | Select-Object -first 1
            $splat = @{
                Message             = "`nSelect file(s) to process from [SpringGreen1]$($top)[/]:"
                Choices             = $list
                ChoiceLabelProperty = 'Name'
                Color               = $SelectionColor
                AllowEmpty          = $True
                PageSize            = $PageSize
            }
            If ($Timeout -gt 0) {
                $splat.Add('TimeoutSeconds',$TimeOut)
            }
            Read-SpectreMultiSelection @splat
        }
    } #end
}
Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.