Can a PowerShell function determine if it is being run as part of a pipeline? I have a function which populates an array with instances of FileInfo
which I would like to "yield" to the pipeline if the function is being run this way or produce some pretty output if the function is being invoked by itself from the command line.
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
}
}
}
Basically, I'm trying to figure out how to implement INVOKED_IN_PIPELINE
. If it is run in a pipeline (e.g. Do-Something | format-table fullname
), I would simply yield the array, but if run directly (e.g. Do-Something
), it would pretty-print the contents of the array to the console.
Is there a way to do this? If there is a more "idiomatic" way to achieve this kind of thing, I would also be interested to know.
This information is available as part of $PSCmdlet.MyInvocation. Here is a cmdlet you can use to experiment with this. What it does it to write out the contents of that property once for any command execution and then passes on the object (so you can manipulate the objects some more with bigger pipelines). What you'll see is that there is a property called PipelineLength
which is equal to 1 when you run this command by itself and increases for each item in the pipeline. Also notice PipelinePosition
. It tells you what position this command is in the pipeline.
NOTE: $PSCmdlet
is only available when you write an advanced function (e.g. have the [CmdletBinding()]
attribute.
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 $_
}
}
Here are some examples:
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
The "idiomatic" way to do this is to output a specific object type and then define formatting data for that object. The object can be a custom (C#/VB-based object) or a named PSObject. The advantage of this approach is that you can just output these objects and if there is no further pipeline output formatting (ie use of a Format
command) then you're defined default output formatting will get used. Otherwise, one of the Format
commands can override that default formatting. Here's an example:
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
Notice how the default output is different the second time we sent $obj
down the pipe. That's because it used the custom formatting instructions provided since there were no explicit formatting commands used.
BTW there is always a pipeline even for $obj
because that is implicitly piped to Out-Default
.
Here is the custom format definition that is defined in a .ps1xml
file that I named 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>
For more examples of how to format custom objects look at the files output by this command:
Get-ChildItem $pshome\*.format.ps1xml
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With