2

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?

AxD
  • 2,714
  • 3
  • 31
  • 53
  • 1
    your script is missing a `process` block :) otherwise you would see only the last item coming from the pipeline cause the default is `end` block – Santiago Squarzon Feb 21 '23 at 21:26
  • Oh, gee!! I didn't know a script block was required for the script root, too. – AxD Feb 21 '23 at 21:27
  • 1
    Thanks a lot, @SantiagoSquarzon. Do you want to convert your comment into an answer? I'd be happy to upvote. – AxD Feb 21 '23 at 21:28
  • I can yeah but I'm missing some clarification, I see you're looking to `Sort-Object` however in a `process` block you have one at a time processing so no sorting is possible. Are you looking to collect all input from the pipeline to then sort it? – Santiago Squarzon Feb 21 '23 at 21:32
  • Yes, indeed. And after few more tests I now know I won't be able to use the pipeline for my purpose. I need to call `Get-ChildItem | Sort-Object` from within my script and accept `Filter`, `SourcePath` and other `Get-ChildItem` parameters in my script to apply these within the script. – However, I still would like to accept your answer as it may be helpful to others. – AxD Feb 21 '23 at 21:44
  • Not really, there is always a way. You could use a `List` to collect all input in the `process` block then work in with that collected input in your `end` block :) – Santiago Squarzon Feb 21 '23 at 21:45
  • Gee, awesome! Sure, that's a great idea! Thank you so much. – AxD Feb 21 '23 at 21:51

1 Answers1

2

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)
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37