Могу ли я определить, работает ли функция PowerShell как часть конвейера?

Может ли функция PowerShell определить, выполняется ли она как часть конвейера? У меня есть функция, которая заполняет массив экземплярами FileInfo, которые я хотел бы "уступить" конвейеру, если функция запускается таким образом или создает довольно красивый вывод, если функция вызывается сама по себе из командной строки.

function Do-Something {
    $file_infos = @()
    # Populate $file_infos with FileInfo instances...

    if (INVOKED_IN_PIPELINE) {
        return $file_infos
    }
    else {
        foreach ($file_info in $file_infos) {
            write-host -foregroundcolor yellow $file_info.fullname
        }
    }
}

В принципе, я пытаюсь понять, как реализовать INVOKED_IN_PIPELINE. Если он запущен в конвейере (например, Do-Something | format-table fullname), я просто выдал бы массив, но если он будет запущен напрямую (например, Do-Something), он будет довольно печатать содержимое массива на консоли.

Есть ли способ сделать это? Если есть более "идиоматический" способ достичь такого рода вещей, мне также будет интересно узнать.

Ответ 1

Эта информация доступна как часть $PSCmdlet.MyInvocation. Вот командлет, который вы можете использовать, чтобы поэкспериментировать с этим. Что он делает, чтобы выписать содержимое этого свойства один раз для выполнения любой команды, а затем передать объект (чтобы вы могли больше манипулировать объектами с более крупными конвейерами). Вы увидите, что есть свойство, называемое PipelineLength, которое равно 1, когда вы запускаете эту команду самостоятельно и увеличивается для каждого элемента в конвейере. Также обратите внимание на PipelinePosition. Он сообщает вам, в какой позиции находится эта команда.

ПРИМЕЧАНИЕ. $PSCmdlet доступен только при написании расширенной функции (например, имеет атрибут [CmdletBinding()].

function test-PSCmdlet
{
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
$test
)
Begin{
    $once = $false
    }
process
    {
        if (!$once)
        {
            write-host ($PSCmdlet.MyInvocation |out-string)
            $once = $true
        }
        Write-Output $_
    }
}

Вот несколько примеров:

PS C:\Users\jsnover.NTDEV> test-PSCmdlet

MyCommand        : test-PSCmdlet
BoundParameters  : {}
UnboundArguments : {}
ScriptLineNumber : 1
OffsetInLine     : 14
HistoryId        : 61
ScriptName       : 
Line             : test-PSCmdlet
PositionMessage  : 
                   At line:1 char:14
                   + test-PSCmdlet <<<< 
InvocationName   : test-PSCmdlet
PipelineLength   : 1
PipelinePosition : 1
ExpectingInput   : False
CommandOrigin    : Runspace


PS C:\Users\jsnover.NTDEV> gps lsass | test-PSCmdlet |Format-table Name,Id -auto

MyCommand        : test-PSCmdlet
BoundParameters  : {[test, System.Diagnostics.Process (lsass)]}
UnboundArguments : {}
ScriptLineNumber : 1
OffsetInLine     : 26
HistoryId        : 62
ScriptName       : 
Line             : gps lsass | test-PSCmdlet |Format-table Name,Id -auto
PositionMessage  : 
                   At line:1 char:26
                   + gps lsass | test-PSCmdlet <<<<  |Format-table Name,Id -aut
                   o
InvocationName   : test-PSCmdlet
PipelineLength   : 3
PipelinePosition : 2
ExpectingInput   : True
CommandOrigin    : Runspace


Name   Id
----   --
lsass 620

Ответ 2

"Идиоматический" способ сделать это - вывести конкретный тип объекта, а затем определить данные форматирования для этого объекта. Объект может быть обычным (объект на основе С#/VB) или именованный PSObject. Преимущество этого подхода заключается в том, что вы можете просто выводить эти объекты, и если нет дальнейшего форматирования вывода конвейера (т.е. Использования команды Format), тогда вы будете определять форматирование по умолчанию, которое будет использоваться. В противном случае одна из команд Format может переопределить это форматирование по умолчанию. Вот пример:

PS> $obj = new-object psobject -Property @{FName = 'John'; LName = 'Doe'; `
                                           BirthDate = [DateTime]"5/7/1965"}
PS> $obj.psobject.TypeNames.Insert(0, "MyNamespace.MyCustomTypeName")
PS> $obj

BirthDate                               FName                            LName
---------                               -----                            -----
5/7/1965 12:00:00 AM                    John                             Doe


PS> Update-FormatData .\MyCustomFormatData.ps1xml
PS> $obj

FName                     LName                     BirthDate
-----                     -----                     ---------
John                      Doe                       5/7/1965 12:00:00 AM

Обратите внимание на то, как вывод по умолчанию отличается от второго, когда мы отправили $obj вниз по каналу. Это потому, что оно использовало пользовательские инструкции форматирования, поскольку не было явных команд форматирования.

Кстати, всегда существует конвейер даже для $obj, потому что он неявно передан на Out-Default.

Вот определение пользовательского формата, которое определено в файле .ps1xml, который я назвал MyCustomFormatData.xml:

<Configuration>
  <ViewDefinitions>
    <View>
      <Name>MyNamespace.MyCustomTypeName</Name>
      <ViewSelectedBy>
        <TypeName>MyNamespace.MyCustomTypeName</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader>
            <Label>FName</Label>
            <Width>25</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>LName</Label>
            <Width>25</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>BirthDate</Label>
            <Width>25</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>FName</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>LName</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>BirthDate</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

Дополнительные примеры того, как отформатировать пользовательские объекты, смотрите на файлы, выводимые этой командой:

Get-ChildItem $pshome\*.format.ps1xml