2

E.g. I want something like head that either accepts an array piped to it and then does select-object -first, or, receives a list of file names as parameters and outputs the first lines of each. So cat $filename | head should work like head $filename

Here's what I've tried so far:

Function head( )
{
    param (
      [switch] $help = $false,
      [parameter(mandatory=$false,ValueFromPipeline=$true)] [object[]] $inputs,
      [parameter(ValueFromRemainingArguments=$true)] [String[]] $files
    )

    If( $help )
    {
        Write-Host "usage: $($MyInvocation.MYCommand) [<file>] <numLines>"
        return
    }


    $lines = 0
    if ($files -and [int]::TryParse($files[0], [ref]$lines)) {
      $null,$files = $files
    } else {
      $lines = 10
    }

    $input | select-object -First $lines
    if ($files) {get-content -TotalCount $lines $files}
}

But this causes the function to ignore the first parameter:

C:\Users\idror.TLV-WPVAJ> head C:\temp\now.ps1
C:\Users\idror.TLV-WPVAJ> head C:\temp\now.ps1 C:\temp\now.ps1
Function head( )
{
    param (
          [switch] $help = $false,
          [parameter(mandatory=$false,ValueFromPipeline=$true)] [object[]] $input,
          [parameter(ValueFromRemainingArguments=$true)] [String[]] $files
        )

        If( $help )
        {
C:\Users\idror.TLV-WPVAJ> $a | head
1
2
3
C:\Users\idror.TLV-WPVAJ> $a | head 1
head : 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.
At line:1 char:6
+ $a | head 1
+      ~~~~~~
    + CategoryInfo          : InvalidArgument: (1:Int32) [head], ParameterBindingException
    + FullyQualifiedErrorId : InputObjectNotBound,head
IttayD
  • 28,271
  • 28
  • 124
  • 178
  • So the piped in array would be _content_ or an array of file names? – briantist Jul 25 '16 at 14:38
  • the content. see update – IttayD Jul 25 '16 at 14:40
  • Is your question about how to write the parameter sets or are you asking us to write the whole function for you? Have you tried anything so far? – briantist Jul 25 '16 at 14:45
  • i have tried and i'm getting all sorts of weird errors. Would like to see the whole function, but written so it works just for a very specific use case, but such that I can change the actual implementation (e.g. tail) – IttayD Jul 25 '16 at 15:18
  • You should [edit] your question with the code you've tried and the specific errors you've gotten. We could maybe help with that. – briantist Jul 25 '16 at 15:21

1 Answers1

1

You can achieve this using Parameter Sets:

function head {
[CmdletBinding()]
param(
    [Parameter(
        ParameterSetName = 'Content',
        ValueFromPipeline = $true,
        Mandatory = $true
    )]
    [String[]]
    $Content ,

    [Parameter(
        ParameterSetName = 'File',
        ValueFromRemainingArguments = $true ,
        Mandatory = $true
    )]
    [String[]]
    $Path,

    [Parameter()]
    [uint64]
    $Count = 5
)

    Begin {
        Write-Verbose "Parameter Set Name: $($PSCmdlet.ParameterSetName)"
    }

    Process {
        Write-Verbose "Content: $Content"
        Write-Verbose "Path: $Path"
        Write-Verbose "Count: $Count"
    }
}

First, take a look at the help output of this by running Get-Help head:

NAME
    head

SYNTAX
   head -Content <string[]> [-Count <uint64>]  [<CommonParameters>]

   head -Path <string[]> [-Count <uint64>]  [<CommonParameters>]

You can see that it interprets 2 different sets, and each parameter is mandatory in its own set.

You can also call it with -Verbose to see a demonstration of how this is working:

# Content by pipeline
'one','two','three' | head -Verbose

# Files as an array
head -Verbose 'file1','file2','file3'

# Files as remaining arguments
head -Verbose 'file1' 'file2' 'file3'
briantist
  • 45,546
  • 6
  • 82
  • 127
  • nice. can i have a situation where I can have both? something like `1,2,3 | head -count 3 c:\temp\foo` output `1,2,3,one,two,three` (where one,two,three are the first lines in foo)? – IttayD Jul 26 '16 at 07:15
  • @IttayD possibly, but it may be an ambiguous parameter set because pipeline binding isn't something that the engine uses to differentiate between parameters; in other words if you didn't call it via pipeline, it wouldn't know whether the single string you sent it was a file name or content (I think). You'd have to experiment with it. See [my answer here for tips on troubleshooting parameter sets](http://stackoverflow.com/a/37074811/3905079). – briantist Jul 26 '16 at 14:59