5

When working with an array of values, indexof can be used to find the position of the value in the array.

#this returns '1', correctly identifying 'blue' in position '1' of the array
$valueArray = @('cup','blue','orange','bicycle')
[array]::indexof($valueArray,'blue')

I would like to use this command to find the position of a file (image) in an array of objects generated with Get-ChildItem, however the returned position is always '-1' no matter where the object I have called for actually is. Note that image123.jpg is in the middle of the array.

$imageArray = Get-ChildItem "C:\Images"
[array]::indexof($imageArray,'image123.jpg')

I have noticed that if I change the array to filenames only, it works returning the actual position of the filename.

$imageArray = Get-ChildItem "C:\Images" | select -expand Name
[array]::indexof($imagesToReview,'image123.jpg')

Is this just the nature of using indexof or is there a way to find the correct position of the image file in the array without converting?

1
  • 3
    Get-ChildItem returns an array of DirectoryInfo and FileInfo objects. Those objects contains various properties and values. You have better results when you select only the value of the name property because that is a string that contains the file name. If you used [array]::indexof($imageArray.Name,'image123.jpg') in your first example, you would have better results. Commented Apr 21, 2020 at 16:02

2 Answers 2

10

The easiest solution here is the following:

$imageArray = Get-ChildItem "C:\Images"
[array]::indexof($imageArray.Name,'image123.jpg')

Explanation:

[array]::IndexOf(array array,System.Object value) searches an array object for an object value. If no match is found, it returns the array lower bound minus 1. Since the array's first index is 0, then it returns the result of 0-1.

Get-ChildItem -Path SomePath returns an array of DirectoryInfo and FileInfo objects. Each of those objects has various properties and values. Just using $imageArray to compare to image123.jpg would be comparing a System.IO.FileInfo object to a String object. PowerShell won't automatically convert a FileInfo object into a string while correctly parsing to find your target value.

When you choose to select a property value of each object in the array, you are returning an array of those property values only. Using $imageArray | Select -Expand Name and $imageArray.Name return an array of Name property values. Name contains a string in your example. This means you are comparing a String to a String when using [array]::IndexOf($imageArray.Name,'image123.jpg').

Sign up to request clarification or add additional context in comments.

Comments

5

The way that .NET by default compares things is just not as forgiving as PowerShell is!

[array]::IndexOf($array, $reference) will go through the array and return the current index when it encounters an item for which the following is true:

$item.Equals($reference)

... which is NOT necessarily the same as doing

$item -eq $reference

For simple values, like numbers and dates and so on, Equals() works exactly like -eq:

PS C:\> $a = 1
PS C:\> $b = 1
PS C:\> $a.Equals($b)  # $true

... which is the reason your first example works as expected!

For more complex objects though, Equals() works a bit differently. Both values MUST refer to the same object, it's not enough that they have similar or even identical values:

PS C:\> $a = New-Object object
PS C:\> $b = New-Object object
PS C:\> $a.Equals($b)    # $false

In the example above, $a and $b are similar (if not identical) - they're both empty objects - but they are not the same object.

Similarly, if we test with your input values, they aren't the same either:

PS C:\> $a = Get-Item "C:\"
PS C:\> $b = "C:\"
PS C:\> $a.Equals($b)    # $false

One of the reasons they can't be considered the same, as AdminOfThings excellently explains, is type mismatch - but PowerShell's comparison operators can help us here!

You'll notice that this works:

PS C:\> $a = Get-Item "C:\"
PS C:\> $b = "C:\"
PS C:\> $b -eq $a
True

That's because the behavior of -eq depends on the left-hand operand. In the example above, "C:\" is a string, so PowerShell converts $a to a string, and all of a sudden the comparison is more like "C:\".Equals("C:\")!

With this in mind, you could create your own Find-IndexOf function to do $reference -eq $item (or any other comparison mechanism you'd like) with a simple for() loop:

function Find-IndexOf
{
  param(
    [array]$Array,
    [object]$Value
  )

  for($idx = 0; $idx -lt $Array.Length; $idx++){
    if($Value -eq $Array[$idx]){
      return $idx
    }
  }

  return -1
}

Now you'd be able to do:

PS C:\> $array = @('','PowerShell is case-insensitive by default')
PS C:\> $value = 'POWERsheLL iS cASe-InSenSItIVe BY deFAuLt'
PS C:\> Find-IndexOf -Array $array -Value $value
1

Or:

PS C:\> $array = Get-ChildItem C:\images
PS C:\> $value = 'C:\images\image123.png'
PS C:\> Find-IndexOf -Array $array -Value $value
5

Adding comparison against a specific property on each of the array items (like the file's Name in your example), we end up with something like this:

function Find-IndexOf
{
  param(
    [array]$Array,
    [object]$Value,
    [string]$Property
  )

  if($Property){
    for($idx = 0; $idx -lt $Array.Length; $idx++){
      if($Value -eq $Array[$idx].$Property){
        return $idx
      }
    }
  }
  else {
    for($idx = 0; $idx -lt $Array.Length; $idx++){
      if($Value -eq $Array[$idx]){
        return $idx
      }
    }
  }

  return -1
}

Find-IndexOf -Array @(Get-ChildItem C:\images) -Value image123.png -Property Name

1 Comment

This is truly above and beyond, thanks so much for the detail to the solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.