Search code examples
functionpowershellparameterspipelinepowershell-4.0

Pipeline to user function parameter problems


For some reason, my program is refusing to work in this scenario:

  • I have a custom function that is meant to replace a cmdlet due to lack of flexibility
  • I am passing this function a file or folder object, through the pipeline

This is the function:

function Get-ChildItemCustom {
    Param(
        [Parameter(ValueFromPipeline=$true)]
        [System.IO.FileSystemInfo] $item,
        [Parameter(ValueFromPipeline=$false)]
        [string] $archive_path
    )
    Process {
        Get-ChildItem $item
    }
}

I want to be able to use this function just like Get-ChildItem: input a [System.IO.FileSystemInfo] object, and get all the children (sorted with some extra criteria that I didn't include here) as output.

This is how I call the function:

Get-ChildItem $parentfolder_path |
    Get-ChildItemCustom |
    Do-SomethingElse

The error returns explains that the results of Get-ChildItem (which are verifiably of type [System.IO.FileSystemInfo]) are being treated as strings.

Cannot convert the "E:\Data\VHG-ITC-Test\New folder\archive" value of type "System.String" to type "System.IO.FileSystemInfo".

The type preceding the parameter was not always there. When $item did not explicitly have a type, the function would misread the input (supposedly only taking the Name property as input):

Get-ChildItem : Cannot find path 'C:\Windows\system32\New folder' because it does not exist.

So the function does not seem to be able to accept the object input properly. I want to avoid using strings at all costs, and just move objects around. Have I setup the parameters wrong? What can I do?


Solution

  • The problem isn't exactly with your function, but with the way Get-ChildItem handles arguments (as Moerwald already suspected in his answer).

    When you call Get-ChildItem with a FileInfo object as an unnamed argument that argument is passed to the first positional parameter (-Path), which expects a string array as input, so the object is cast to a string. However, in some situations casting FileInfo objects to a string expands the FullName property, while in others it expands just the Name property (I can't explain how PowerShell decides when to pick which, though). The latter is what's happening in your case. And since Get-ChildItem sees just a name, not a full path, it's looking for the item in the current working directory, which fails.

    There are a number of ways to avoid this issue, one of which Moerwald has already shown. Others are:

    • using a pipeline for passing $item to Get-ChildItem:

      function Get-ChildItemCustom {
          Param(
              [Parameter(ValueFromPipeline=$true)]
              [IO.FileSystemInfo]$item,
      
              [Parameter(ValueFromPipeline=$false)]
              [string]$archive_path
          )
      
          Process {
              $item | Get-ChildItem
          }
      }
      
    • passing the full path by mapping the property by name:

      function Get-ChildItemCustom {
          Param(
              [Parameter(
                  ValueFromPipeline=$true,
                  ValueFromPipelineByPropertyName=$true
              )]
              [string[]]$FullName,
      
              [Parameter(ValueFromPipeline=$false)]
              [string]$archive_path
          )
      
          Process {
              Get-ChildItem $FullName
          }
      }
      

    Personally, I'd prefer the last variant (passing by property name).