Могу ли я определить, работает ли функция 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