powershellpowershell-7.3powershell-7

How can I get all pipeline elements at a time?


I created this very simple PowerShell script:

using namespace System.IO

[CmdletBinding(SupportsShouldProcess)]
param
(
  [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)][FileInfo[]]$Files
)
$Files.Length
$Files | Sort-Object | ForEach-Object {$_.Name}

When I call it with any result of a Get-ChildItem call, $Files.Length is always 1, no matter how many files are in a directory:

PS C:\Temp> Get-ChildItem -File C:\Windows\ | .\Rename-Test.ps1
1
WMSysPr9.prx

What did I do wrong?


Solution

  • Basically your script is only missing a process block otherwise, the default block is end thus you would be only seeing the last element coming from the pipeline. A minimal example of how you can approach it:

    using namespace System.IO
    
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [FileInfo[]] $Files
    )
    
    process {
        foreach($file in $Files) {
            if($PSCmdlet.ShouldProcess($file.Name, 'Doing something')) {
                $file.FullName
            }
        }
    }
    

    As you may note, since the -Files parameter is typed [FileInfo[]] (array of FileInfo instances), a loop is required in case the argument is passed via named parameter or positionally.

    If however you need to collect all input, i.e. for sorting it, then you would be needing a List<T> to collect each object in the process block and then work with the collected input in the end block, for example:

    using namespace System.IO
    
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [FileInfo[]] $Files
    )
    
    begin {
        $items = [System.Collections.Generic.List[FileInfo]]::new()
    }
    process {
        $items.AddRange($Files)
    }
    end {
        $items | Sort-Object Length
    }
    

    Then for the above, both ways would work fine:

    # from pipeline
    Get-ChildItem -File | .\myscript.ps1
    
    # from positional binding
    .\myscript.ps1 (Get-ChildItem -File)