ekkis
ekkis

Reputation: 10226

Argument list and the pipeline

as an adjunct to this issue: How do I force declared parameters to require explicit naming? I am struggling with pipelines. Suppose I want the behaviour that this declares:

param(
   $installdir, 
   $compilemode, 
   [Parameter(Position=0, ValueFromRemainingArguments=$true)] $files
   )

namely, that I can call my script like this:

c:\> MyScript -installdir c:\ file-1.txt file-2.txt file-3.txt

but now I want to also be able to do it this way:

c:\> gi file-*.txt |MyScript -installdir c:\

I might think of adding a decoration to the parameter like this:

param(
   $installdir, 
   $compilemode, 
   [Parameter(
       Position=0, 
       ValueFromRemainingArguments=$true, 
       ValueFromPipeline=$true
   )] $files
   )

but what actually happens is I only get 1 argument into my parameter i.e. instead of getting an array with all the files that gi produced, I get only the first in the list.

A second way I attempted this was by using the $input variable (instead of using the ValueFromPipeline decorator), but then in trying to call the script I get the error:

The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.

where can I go from here?

Upvotes: 0

Views: 1858

Answers (1)

Keith Hill
Keith Hill

Reputation: 201602

You could declare it without ValueFromRemainingArguments:

param(
   [Parameter(
       Position=0, 
       ValueFromPipeline=$true,
       ValueFromPipelineByPropertyName=$true)]
   [Alias('PSPath')]
   [string[]]
   $files,
   $installdir, 
   $compilemode
   )

And then pass in multiple files as an array using the comma operator e.g.:

 MyScript -installdir c:\ file-1.txt,file-2.txt,file-3.txt

Note: In order to accept input from commands like Get-Item and Get-ChildItem, use ValueFromPipelineByPropertyName and add a parameter alias "PSPath" that will find the PSPath property on the objects output by Get-Item/Get-ChildItem.

I have test this in ISE and it works fine:

function foo
{
    param(
       [Parameter(
           Position=0, 
           ValueFromPipeline=$true,
           ValueFromPipelineByPropertyName=$true)]
       [Alias('PSPath')]
       [string[]]
       $files,
       $installdir, 
       $compilemode
       )

    process {
        foreach ($file in $files) {
            "File is $file, installdir: $installdir, compilemode: $compilemode"
        }
    }
}

foo a,b,c -installdir c:\temp -compilemode x64
ls $home -file | foo -installdir c:\bin -compilemode x86

FYI, this is a template I use all the time to create commands that take pipeline input or array input, as well as wildcard paths:

function Verb-PathLiteralPath
{
    [CmdletBinding(DefaultParameterSetName="Path",
                   SupportsShouldProcess=$true)]
    #[OutputType([output_type_here])] # Uncomment this line and specify the output type  of this
                                      # function to enable Intellisense for its output.
    param(
        [Parameter(Mandatory=$true, 
                   Position=0, 
                   ParameterSetName="Path", 
                   ValueFromPipeline=$true, 
                   ValueFromPipelineByPropertyName=$true,
                   HelpMessage="Path to one or more locations.")]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]
        $Path,

        [Alias("PSPath")]
        [Parameter(Mandatory=$true, 
                   Position=0, 
                   ParameterSetName="LiteralPath", 
                   ValueFromPipelineByPropertyName=$true,
                   HelpMessage="Literal path to one or more locations.")]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $LiteralPath
    )

    Begin 
    { 
        Set-StrictMode -Version Latest
    }

    Process 
    {
        if ($psCmdlet.ParameterSetName -eq "Path")
        {
            if (!(Test-Path $Path)) {
                $ex = new-object System.Management.Automation.ItemNotFoundException "Cannot find path '$Path' because it does not exist."
                $category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                $errRecord = new-object System.Management.Automation.ErrorRecord $ex, "PathNotFound", $category, $Path
                $psCmdlet.WriteError($errRecord)
            }

            # In the -Path (non-literal) case, resolve any wildcards in path
            $resolvedPaths = $Path | Resolve-Path | Convert-Path
        }
        else 
        {
            if (!(Test-Path $LiteralPath)) {
                $ex = new-object System.Management.Automation.ItemNotFoundException "Cannot find path '$LiteralPath' because it does not exist."
                $category = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                $errRecord = new-object System.Management.Automation.ErrorRecord $ex, "PathNotFound", $category, $LiteralPath
                $psCmdlet.WriteError($errRecord)
            }

            # Must be -LiteralPath
            $resolvedPaths = $LiteralPath | Convert-Path
        }

        foreach ($rpath in $resolvedPaths) 
        {
            if ($pscmdlet.ShouldProcess($rpath, "Operation"))
            {
                # .. process rpath
            }
        }  
    }

    End
    {
    }
}

Upvotes: 3

Related Questions