Powershell Custom Object Confusion

I was working on a script trying to output two different custom objects; one after another and was having problems, so to simplify, I stripped all the code down to just the minimum:

$beep = new-object -TypeName PSObject
$beep | Add-Member -MemberType NoteProperty -Name "Entry1" -Value "beep1"
$beep | Add-Member -MemberType NoteProperty -Name "Entry2" -Value "beep1"
$beep

$boop = new-object -TypeName PSObject
$boop | Add-Member -MemberType NoteProperty -Name "Entry1" -Value "boop1"
$boop | Add-Member -MemberType NoteProperty -Name "Entry2" -Value "boop1"
$boop

When I run this, it appears the objects are combined together. When I do a get-member, it seems to just show one object. Why?

In the code I am actually trying to finish, one object is a custom object like above, but the other it a selected.system.int32. When I try to output one after the other only the first one outputs. If I flip the order; same thing, the first object gets output. What am I doing wrong / don't understand?

@JamesQ - I guess what is confusing me is when I do:

$beep = new-object -TypeName PSObject
$beep | Add-Member -MemberType NoteProperty -Name "Entry1" -Value "beep1"
$beep | Add-Member -MemberType NoteProperty -Name "Entry2" -Value "beep1"
$beep | get-member

$boop = new-object -TypeName PSObject
$boop | Add-Member -MemberType NoteProperty -Name "Entry1" -Value "boop1"
$boop | Add-Member -MemberType NoteProperty -Name "Entry2" -Value "boop1"
$boop | get-member

I get:

TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Entry1      NoteProperty System.String Entry1=beep1
Entry2      NoteProperty System.String Entry2=beep1
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
Entry1      NoteProperty System.String Entry1=boop1
Entry2      NoteProperty System.String Entry2=boop1

I was trying to keep the question simple, but I think I've made it too ambiguous so...

I am trying to reuse some code:

function Get-NetworkConfig($computerName) {
  Get-WmiObject Win32_NetworkAdapter -Filter 'NetConnectionStatus=2' |
    ForEach-Object {
      $result = 1 | Select-Object Name, IP, MAC
      $result.Name = $_.Name
      $result.MAC = $_.MacAddress
      $config = $_.GetRelated('Win32_NetworkAdapterConfiguration') 
      $result.IP = $config | Select-Object -expand IPAddress
      $result
    }
}

then in the same script something like:

function Get-ComputerInfo($computerName) {
    $operatingSystem = Get-WMIObject -computername $computerName win32_operatingsystem
    $computerInfo = new-object -TypeName PSObject
    $computerInfo | Add-Member -MemberType NoteProperty -Name "Computer Name" -Value ("$computerName")
    $computerInfo | Add-Member -MemberType NoteProperty -Name "Operating System" -Value ("$operatingSystem.Caption")
    $computerInfo
}

$computerNames = $args
foreach ($computerName in $computerNames) {
    Get-ComputerInfo($computerName)
    Get-NetworkConfig($computerName)
}

Only the output from get-computerinfo is displayed, then a bunch of blank lines where get-networkconfig would have output.

If I reverse the order; only the first function gives output, the second only spits blank lines.

Why can't I just call two functions in sequence like this?


Solution 1:

When I do a Get-Member, it seems to just show one object.

Get-Member shows information about the distinct types in the collection of inputs, which means that the first occurrence of each type is reported on, with subsequent occurrences skipped.

In your case, both input objects are of type [System.Management.Automation.PSCustomObject], so Get-Member will report just that one shared type.

For instance, 1, 2 | Get-Member reports information about System.Int32 once.


one object is a custom object like above, but the other it a selected.system.int32. When I try to output one after the other only the first one outputs.

PowerShell's default output formatting defaults to implicit use of Format-Table for custom objects with up to 4 properties.

If you output multiple objects that have different types, and the first object defaults to implicit Format-Table output, that first object's type alone determines what properties (columns) to show in the resulting table.

  • Note: If the first object's type happens to have formatting data associated with it (as reported by Get-FormatData), subsequent objects do print, albeit invariably via implicit Format-List formatting.

If subsequent objects do not have any of the same properties that the first object has, they simply print a blank line; if they have some of the same properties, only those are printed; any additional properties are ignored.

It is important to note that this is just a display problem, however: all objects that were output are still present; if you send the output to another command for further processing instead of directly to the console, they'll all be there.

A simple example:

PS> [pscustomobject] @{ one = 1; two = 2 }, [pscustomobject] @{ three = 3 }
    
one two
--- ---
  1   2
 

Note how the 2nd custom object resulted in just a blank line, because it has neither properties .one nor .two.

You can work around the problem by using explicit formatting commands applied to each object to output:

PS> [pscustomobject] @{ one = 1; two = 2 }, [pscustomobject] @{ three = 3 } |
      ForEach-Object { Format-Table -InputObject $_ }

one two
--- ---
  1   2



three
-----
    3

The same approach as individual output commands:

[pscustomobject] @{ one = 1; two = 2 } | Format-Table
[pscustomobject] @{ three = 3 } | Format-Table

As Mark Wragg's blog post explains, all output produced by a given script - even across separate commands - is sent to the same pipeline.
(You can think of a command line submitted interactively as an implicit script.)

For a more detailed discussion of how a mix of types in a single pipeline is handled in terms of display formatting, see this answer.


Why using an explicit formatting command helps:

By explicitly piping to a Format-* cmdlet (e.g, [pscustomobject] @{ one = 1; two = 2 } | Format-Table), you're actually sending formatting objects (various [Microsoft.PowerShell.Commands.Internal.Format.*] types) to the pipeline, and PowerShell then effectively passes them through for display.

An alternative is to use a generic workaround: if you pipe to Out-Host instead (e.g., [pscustomobject] @{ one = 1; two = 2 } | Out-Host), in which case:

  • you bypass the pipeline and print directly to the console (if you're running PowerShell in a regular console window),
  • and the object's default formatting view is applied.

It is important to note that these workarounds are suitable only for display purposes, because the original objects are lost in the process:

  • When you pipe to a Format-* cmdlet explicitly, you replace the original object with objects containing formatting instructions, which are useless for further processing.

  • When you pipe to Out-Host, you send nothing to the script's pipeline.