Search code examples
powershellpropertiescompareobject

Compare-Object (powershell) : return a property but don't compare with it


I am using Compare-Object to compare 2 bidimensional arrays issued by Get-ChildItem.

I am asking it to compare using the Name property using -Property 'Name'

This returns a table with the Name property and SideIndicator.

The problem is that I would like to know not only the name of the file, but also its complete path; if I ask Compare-Object ... -Property @{ Expression = {"$($_.Directory)\$($_.Name)"} }, it compares using the path, which is not what I want either (this tool I'm coding will be to sync up 2 folders, so obviously the paths are different, hence getting all the files scored as different).

Please let me know if you need more details !


Solution

  • If you use Compare-Object with -Property in order to compare the two input collections by one or more specific property values only, by default the output objects contain those properties only, alongside the .SideIndicator property that signals which of the two input collections a given difference came from:

    # Sample input collections.
    $c1 = @(Get-Date) # a single-element array containing a [datetime] instance
    $c2 = @()         # empty array
    
    # Due to -Property Day, the output object contains only the .Day property value, 
    # not the whole date.
    # -> [pscustomobject] @{ Day = 21; SideIndicator = '<=' }
    Compare-Object -Property Day -ReferenceObject $c1 -DifferenceObject $c2
    
    • Note: Only if you omit -Property - resulting in whole-object comparisons (which with complex objects are typically meaningless) - do you get the whole input objects by default, namely in the .InputObject property of the [pscustomobject] instances that are emitted.

    To instead pass the whole original objects through, use the -PassThru switch.

    That is, instead of emitting [pscustomobject] instances with a .SideIndicator property, -PassThru causes the input objects themselves to be emitted.
    However, so that you can still determine which side (input collection) a given object came from, the original objects are decorated with a .SideIndicator property, using PowerShell's ETS (Extended Type System).

    $c1 = @(Get-Date)
    $c2 = @()
    
    # Due to adding -PassThru, the output is now the whole date.
    $differingDates = 
      Compare-Object -Property Day -PassThru $c1 $c2
    
    # The original [datetime] instance was returned, decorated with a
    # .SideIndicator property.
    $differingDates.SideIndicator # -> '<='
    

    Caveats:

    • If you combine -PassThru with -IncludeEqual, it is invariably only the LHS (-ReferenceObject) object that is passed through for objects that compare as equal (i.e. whose .SideIndicator property contains ==); that is, if two objects compare equal with respect to the given properties but differ with respect to others, you'll only have access to those other properties from the LHS objects.

      # Sample input collections.
      $c1 = @(Get-Date -Year 2023)
      $c2 = @(Get-Date -Year 2022)
      
      # Compare by *day*, with respect to which the two dates are equal.
      # !! Invariably outputs the *2023* date.
      Compare-Object -Property Day -PassThru -IncludeEqual $c1 $c2
      
    • The ETS .SideIndicator property is usually invisible, such as in the output from the example above, but may situationally unexpectedly surface, such as when you pass a decorated object to ConvertTo-Json
      (pipe to Select-Object -Property * -ExcludeProperty SideIndicator to avoid this).[1]

      # Sample type with only one property, .Bar
      class Foo { [int] $Bar = 42 }
      
      # Sample input collections.
      $c1 = @([Foo]::new())
      $c2 = @()
      
      # !! JSON includes .SideIndicator.
      # !! -> {"Bar":42,"SideIndicator":"<="}
      Compare-Object -PassThru $c1 $c2 |
        ConvertTo-Json -Compress
      

    [1] In PowerShell (Core) 7.2+, ConvertTo-Json now automatically and invariably ignores ETS properties of [string] and [datetime] instances, specifically. See GitHub issue #5797 for the original discussion that led to this change.