Search code examples
powershellbooleancomparisonequalityobject-reference

Alternative to "-Contains" that compares value instead of reference


I have a list of file objects, and I want to check whether a given file object appears inside that list. The -Contains operator is nearly what I'm looking for, but it appears that -Contains uses a very strict equality test where object references have to be identical. Is there some less strict alternative? I want the $booleanvariable in the code below to return True the second time as well as the first time.

PS C:\Users\Public\Documents\temp> ls


    Directory: C:\Users\Public\Documents\temp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       14.08.2017     18.33              5 file1.txt
-a----       14.08.2017     18.33              5 file2.txt


PS C:\Users\Public\Documents\temp> $files1 = Get-ChildItem .
PS C:\Users\Public\Documents\temp> $files2 = Get-ChildItem .
PS C:\Users\Public\Documents\temp> $file = $files1[1]
PS C:\Users\Public\Documents\temp> $boolean = $files1 -Contains $file
PS C:\Users\Public\Documents\temp> $boolean
True
PS C:\Users\Public\Documents\temp> $boolean = $files2 -Contains $file
PS C:\Users\Public\Documents\temp> $boolean
False
PS C:\Users\Public\Documents\temp>

Solution

  • Get-ChildItem returns objects of the type [System.IO.FileInfo].

    Get-ChildItem  C:\temp\test\2.txt | Get-Member | Select-Object TypeName -Unique
    
    TypeName
    --------
    System.IO.FileInfo
    

    As PetSerAl mentioned in the comments [System.IO.FileInfo] does not implement IComparable or IEquatable.

    [System.IO.FileInfo].GetInterfaces()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    ISerializable
    

    Without these, as you noticed PowerShell will only support reference equality. Lee Holmes' has a great blog post on this.

    The solution to this is then to make comparisons on sub-properties that are comparable. You can choose a specific property that would be unique, like Fullname as Mathias R Jessen mentioned. With the drawback that if the other properties are different, they will not be evaluated.

    'a' | Out-File .\file1.txt
    $files = Get-ChildItem .
    'b' | Out-File .\file1.txt
    $file = Get-ChildItem .\file1.txt
    $files.fullname -Contains $file.fullname
    
    True
    

    Alternatively, you could use the Compare-Object cmdlet which will compare all of the properties between the two objects (or the specific properties you choose with -Property).

    Using the -IncludeEqual -ExcludeDifferent flags of Compare-Object, we can find all the objects with matching properties. Then when an array is cast to a [bool], if it is non-empty it will be $True and if empty it will be $False.

    'a' | Out-File .\file1.txt
    $files = Get-ChildItem .
    $file = Get-ChildItem .\file1.txt
    [bool](Compare-Object $files $file -IncludeEqual -ExcludeDifferent)
    
    True
    
    
    'a' | Out-File .\file1.txt
    $files = Get-ChildItem .
    'b' | Out-File .\file1.txt
    $file = Get-ChildItem .\file1.txt
    [bool](Compare-Object $files $file -IncludeEqual -ExcludeDifferent)
    
    False
    

    Compare-Object can be resource intensive, so it's best to use other forms of comparison when possible.