Search code examples
powershellcontainsequality

Get newly added value in Array Powershell


for (;;) {

  #Get All Files from the Folder
    
  $FolderItems = @(Get-PnPFolderItem -FolderSiteRelativeUrl $FolderURL -ItemType File)
    
  Write-Host "Total Number of Files in the Folder:" $FolderItems.Count
  if ($FolderItems.Count -gt $oldCount) {
    foreach ($item in $FolderItems) {
      if ($oldFolderItems -contains $item) {                
      }
      else {
        Write-Host $item.Name
      }
            
    } 
  }
  $oldCount = $FolderItems.Count    
  $oldFolderItems = $FolderItems
    
  timeout 180   

}

It prints all the names instead of the one new item


Solution

  • tl;dr

    Replace your foreach loop with the following call to Compare-Object:

    # Compare the new and the old collection items by their .Name property
    # and output the name of those that are unique to the new collection.
    Compare-Object -Property Name $FolderItems $oldFolderItems |
      Where-Object SideIndicator -eq '<=' |
        ForEach-Object Name
    

    You should also initialize $oldFolderItems to $null and $oldCount to 0, to be safe, and - unless you want all names to be output in the first iteration - change the enclosing if statement to:
    if ($oldFolderItems -and $FolderItems.Count -gt $oldCount) { # ...

    Note: The immediate - but inefficient - fix to your attempt would have been the following, for the reasons explained in the next section:

    if ($oldFolderItems.Name -contains $item.Name) { # Compare by .Name values
    

    Note: $oldFolderItems.Name actually returns the array of .Name property values of the elements in collection $oldFolderItems, which is a convenient feature named member-access enumeration.


    As for what you tried:

    It's unclear what .NET type Get-PnPFolderItem returns instances of, but it's fair to assume that the type is a .NET reference type (as opposed to a value type).

    Unless a reference type is explicitly designed to compare its instances based on identifying properties,[1] reference equality is tested for in equality test-based operations such as -contains (but also in other equality-comparison operations, such as with -in and -eq), i.e. only two references to the very same instance are considered equal.

    Therefore, using -contains in your case won't work, because the elements of the collections - even if they conceptually represent the same objects - are distinct instances that compare as unequal.

    A simplified example, using System.IO.DirectoryInfo instances, as output by Get-Item:

    # !! Returns $false, because the two [System.IO.DirectoryInfo]
    # !! instances are distinct objects.
    @(Get-Item /) -contains (Get-Item /)
    

    Therefore, instances of .NET reference types must be compared by the value of an identifying property (if available, such as .Name in this case) rather than as a whole.

    To discover whether a given instance is one of a .NET reference type, access the type's .IsValueType property: a return value of $false indicates a reference type; e.g.:

    (Get-Item /).GetType().IsValueType # -> $false -> reference type
    
    # Equivalent, with a type literal
    [System.IO.DirectoryInfo].IsValueType # -> $false
    

    [1] A notable example is the [string] type, which, as an exception, generally behaves like a value type, so that the following is still $true, despite technically distinct instances being involved: $s1 = 'foo'; $s2 = 'f' + 'oo'; $s1 -eq $s2