I\'m trying to create a simple one line Powershell command that\'ll list all TCP and UDP ports of a given process name.
If I run the lines one by one, it produces the ex
TLDR: it's by design to solve a complex issue (see Github issue #4552: Enable formatting of different object types in same pipeline).
What happens in the first example, is that each command outputs their object data individually. In this case, PowerShell will refer to the format file "C:\Windows\System32\WindowsPowerShell\v1.0\Modules\NetTCPIP\Tcpip.Format.ps1xml"
to see what the default formatting for the object is. In this case, from the format file, PowerShell is told to format both objects to a table based on their type.
When you chain the commands into one, the First object will determine the output format for the entire line. In this example, it will be in the Microsoft.Management.Infrastructure.CimInstance#ROOT/StandardCimv2/MSFT_NetTCPConnection
format. Since the second command outputs an object of type .../MSFT_NetUDPEndpoint
it cannot be formatted in the same way. Even though it could share the same columns, since the specific object formatting is defined, PowerShell then defaults to outputting the object in fallback, best effort Format-List *
format.
This is by design because it's hard to determine individual formatting on the fly especially when you start having arrays with mixed types. They have decided that it is therefore better to use the first object to determine type, and then handle different objects with a fallback.
We can do some testing to see the different ways that PowerShell handles formatting:
#For brevity sake, let's assign variables for our examples:
$processName = "outlook"
$processIds = Get-Process $processName
$TCP = Get-NetTCPConnection | ? {$_.OwningProcess -in $processIds.Id}
$UDP = Get-NetUDPEndpoint | ? {$_.OwningProcess -in $processIds.Id}
First individually:
PS> $TCP
LocalAddress LocalPort RemoteAddress RemotePort State AppliedSetting OwningProcess
------------ --------- ------------- ---------- ----- -------------- -------------
0.0.0.0 65045 0.0.0.0 0 Bound 24200
0.0.0.0 56125 0.0.0.0 0 Bound 24200
PS> $UDP
LocalAddress LocalPort
------------ ---------
:: 5353
0.0.0.0 5353
This is exactly what we want. However, when we chain our objects the output will be:
PS> $TCP; $UDP
LocalAddress LocalPort RemoteAddress RemotePort State AppliedSetting OwningProcess
------------ --------- ------------- ---------- ----- -------------- -------------
0.0.0.0 65045 0.0.0.0 0 Bound 24200
0.0.0.0 56125 0.0.0.0 0 Bound 24200
Caption :
Description :
ElementName :
InstanceID : ::++5353
CommunicationStatus :
DetailedStatus :
HealthState :
InstallDate :
Name :
OperatingStatus :
OperationalStatus :
PrimaryStatus :
Status :
StatusDescriptions :
AvailableRequestedStates :
EnabledDefault : 2
EnabledState :
OtherEnabledState :
RequestedState : 5
TimeOfLastStateChange :
TransitioningToState : 12
AggregationBehavior :
Directionality :
CreationTime : 2019-04-15 9:05:09 AM
LocalAddress : ::
LocalPort : 5353
OwningProcess : 24200
PSComputerName :
Caption :
Description :
ElementName :
InstanceID : 0.0.0.0++5353
CommunicationStatus :
DetailedStatus :
HealthState :
InstallDate :
Name :
OperatingStatus :
OperationalStatus :
PrimaryStatus :
Status :
StatusDescriptions :
AvailableRequestedStates :
EnabledDefault : 2
EnabledState :
OtherEnabledState :
RequestedState : 5
TimeOfLastStateChange :
TransitioningToState : 12
AggregationBehavior :
Directionality :
CreationTime : 2019-04-15 9:05:09 AM
LocalAddress : 0.0.0.0
LocalPort : 5353
OwningProcess : 24200
PSComputerName :
The first object displays properly, and the second falls back to a Format-List *
. Now, let's add in a Select
statement:
PS> $TCP | Select LocalAddress, LocalPort ; $UDP
LocalAddress LocalPort
------------ ---------
0.0.0.0 65045
0.0.0.0 56125
:: 5353
0.0.0.0 5353
Here we see that since we are transforming the TCP object into a PSCustomObject
, with the Select
statement, our $UDP
object can "fit" into the table format, it flows in with the rest of the objects in the pipeline! (Note: this could be unexpected, as you don't know where one object ends and the next begins!)
The final workaround is to essentially "flush" the pipeline by using Out-String
:
PS> $TCP | Out-String; $UDP
LocalAddress LocalPort RemoteAddress RemotePort State AppliedSetting OwningProcess
------------ --------- ------------- ---------- ----- -------------- -------------
0.0.0.0 65045 0.0.0.0 0 Bound 24200
0.0.0.0 56125 0.0.0.0 0 Bound 24200
LocalAddress LocalPort
------------ ---------
:: 5353
0.0.0.0 5353
This gives us the output we likely would like to see, but it's probably still not best practice to chain multiple object types in the same line.