If you know that the value occurs only once in the array, the [array]::IndexOf() method is a pretty good way to go:
$array = 'A','B','C'
$item = 'B'
$ndx = [array]::IndexOf($array, $item)
Besides being terse and to the point, if the array is very large the performance of this approach is quite a bit better than using a PowerShell cmdlet like Where-Object. Still, it will only find the first occurrence of the specified item. But you can use the other overload of IndexOf to find the next occurrence:
$ndx = [array]::IndexOf($array, $item, $ndx+1)
$ndx will be -1 if the item isn't found.
Answer from Keith Hill on Stack OverflowHow to find the index of an item in an array?
powershell - Array.Find and IndexOf for multiple elements that are exactly the same object - Stack Overflow
How to find the index of an element in an array by its value using Powershell - Stack Overflow
How can I find the index of a String array element using the [Array]::FindIndex method in PowerShell? - Stack Overflow
If you know that the value occurs only once in the array, the [array]::IndexOf() method is a pretty good way to go:
$array = 'A','B','C'
$item = 'B'
$ndx = [array]::IndexOf($array, $item)
Besides being terse and to the point, if the array is very large the performance of this approach is quite a bit better than using a PowerShell cmdlet like Where-Object. Still, it will only find the first occurrence of the specified item. But you can use the other overload of IndexOf to find the next occurrence:
$ndx = [array]::IndexOf($array, $item, $ndx+1)
$ndx will be -1 if the item isn't found.
Use a for loop (or a foreach loop that iterates over the array index...same difference). I don't know of any system variable that holds the current array index inside a foreach loop, and I don't think one exists.
# example array
$array = "A", "B", "C"
$item = "B"
0..($array.Count - 1) | Where { $array[
item }
For example i want to print a row from a table that is made out of 3 arrays by the value of an item in one of the arrays(name btw)
You can do this:
b.Count-1)) | where {
_] -eq 'D'}
1
3
6
mjolinor's answer is conceptually elegant, but slow with large arrays, presumably due to having to build a parallel array of indices first (which is also memory-inefficient).
It is conceptually similar to the following LINQ-based solution (PSv3+), which is more memory-efficient and about twice as fast, but still slow:
$arr = 'A','D','B','D','C','E','D','F'
[Linq.Enumerable]::Where(
[Linq.Enumerable]::Range(0, $arr.Length),
[Func[int, bool]] { param(
arr[$i] -eq 'D' }
)
While any PowerShell looping solution is ultimately slow compared to a compiled language, the following alternative, while more verbose, is still much faster with large arrays:
PS C:\> & { param(
val)
$i = 0
foreach (
arr) { if (
val) { $i } ++$i }
} ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Note:
Perhaps surprisingly, this solution is even faster than Matt's solution, which calls
[array]::IndexOf()in a loop instead of enumerating all elements.Use of a script block (invoked with call operator
&and arguments), while not strictly necessary, is used to prevent polluting the enclosing scope with helper variable$i.The
foreachstatement is faster than theForeach-Objectcmdlet (whose built-in aliases are%and, confusingly, alsoforeach).Simply (implicitly) outputting
$ifor each match makes PowerShell collect multiple results in an array.- If only one index is found, you'll get a scalar
[int]instance instead; wrap the whole command in@(...)to ensure that you always get an array.
- If only one index is found, you'll get a scalar
While
$iby itself outputs the value of$i,++$iby design does NOT (though you could use(++$i)to achieve that, if needed).Unlike
Array.IndexOf(), PowerShell's-eqoperator is case-insensitive by default; for case-sensitivity, use-ceqinstead.
It's easy to turn the above into a (simple) function (note that the parameters are purposely untyped, for flexibility):
function get-IndicesOf($Array, $Value) {
$i = 0
foreach (
Array) {
if (
Value) { $i }
++$i
}
}
# Sample call
PS C:\> get-IndicesOf ('A','D','B','D','C','E','D','F') 'D'
1
3
6
Hey guys I'm new to scripting and I'm wondering if anyone could help me with this. Is there a way to find the index number of a certain entry in an array and store it as a new variable?
Say we have an array called $animals which contains the names of various animals. Like so:
$animals = ('dog', 'cat', 'duck', 'horse')And the script asks the user to input the name of an animal:
$input = read-host
And then some kind of "if" statement to check if $input is in $animals (I know how to do this part), and if it is it stores the index number of the array entry as $x.
Is this possible?
Just use .net:
$input = read-host
$x = [array]::IndexOf($animals,$input)
$x will equal -1 if the item does not exist in the array, otherwise it'll equal the index of the array item, provable by:
$animals[$x]
doesn't look like there is an indexOf() method for array in powershell, which is what you're looking for.
This is more cumbersome, but should work:
$i = 0
while ($i -lt $a.length)
{
if ($a[$i] -eq $input)
{
return $i
}
$i++
}
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').
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
I am not sure it's possible with an "automatic" variable. You can always declare one for yourself and increment it:
$letters = { 'A', 'B', 'C' }
$letters | % {$counter = 0}{...;$counter++}
Or use a for loop instead...
for ($counter=0; $counter -lt $letters.Length; $counter++){...}
.NET has some handy utility methods for this sort of thing in System.Array:
PS> $a = 'a','b','c'
PS> [array]::IndexOf($a, 'b')
1
PS> [array]::IndexOf($a, 'c')
2
Good points on the above approach in the comments. Besides "just" finding an index of an item in an array, given the context of the problem, this is probably more suitable:
$letters = { 'A', 'B', 'C' }
$letters | % {$i=0} {"Value:$_ Index:$i"; $i++}
Foreach (%) can have a Begin sciptblock that executes once. We set an index variable there and then we can reference it in the process scripblock where it gets incremented before exiting the scriptblock.
Hi All,
I'm trying to figure out how to stream line something.
When I normally get a large result from a query, and I want to return just one that's in the middle of the pool, I end up having to hunt fish in a barrel. So let’s say I store the results of a query into $qResult (500 items). This result set has some specific values in the middle of that group of results. I know I can out-gridview to see and search through the results, and I do in fact, do this. Once I do find the specific result, I want to just output that specific result so I can copy and paste the information for documentation purposes. This is where the hunting begins.
I end up doing something like:
$qResult[200..250]
$qResult[251..300]
…
…
Until I can identify the index number of the specific result.
I found a method that lets me add an artificial index number into the array and that's helpful but cumbersome. That method looks something like:
$l=0
`$ampEvents | forEach-Object { New-Object psObject ``
`-Property @{'LN'=$l; ``
`'computer' = $_.computer; ``
`'connector_guid' = $_.connector_guid; ``
`'date' = $_.date; ``
`'detection_id' = $_.detection_id; ``
`'error' = $_.error; ``
`'event_type' = $_.event_type; ``
`'event_type_id' = $_.event_type_id; ``
`'file' = $_.file; ``
`'group_guids' = $_.group_guids; ``
`'id' = $_.id; ``
`'severity' = $_.severity; ``
`'timestamp' = $_.timestamp; ``
`'timestamp_nanoseconds' = $_.timestamp_nanoseconds; ``
`};``
`$l ++ ``
}
I cobbled together this snippet to help make the property assignments but it's still messy:
($ampEvents[0] | gm | ? {$_.MemberType -ne "Method"}).Name
$propList = ($ampEvents[0] | gm | ? {$_.MemberType -ne "Method"}).Name
foreach ($prop in $propList){
$propLine = "'" + $prop + "' = " + '$_.' + $prop + '; \'`
Write-Output $propLine
}
That outputs:
`'computer' = $_.computer; ``
`'connector_guid' = $_.connector_guid; ``
`'date' = $_.date; ``
`'detection_id' = $_.detection_id; ``
`'event_type' = $_.event_type; ``
`'event_type_id' = $_.event_type_id; ``
`'file' = $_.file; ``
`'group_guids' = $_.group_guids; ``
`'id' = $_.id; ``
`'severity' = $_.severity; ``
`'timestamp' = $_.timestamp; ``
`'timestamp_nanoseconds' = $_.timestamp_nanoseconds; ``
I can just copy and paste that into the object properties in the earlier code now.
I was wondering if there is a better way to approach this. I tried this and it was not successful:
$l=0
$objProps = New-Object psObject
$objProps | Add-Member -MemberType NoteProperty -Name "LN" -Value $l
foreach ($prop in $propList){
$objProps | Add-Member -MemberType NoteProperty -Name $prop -Value $('$_.' + $prop)
}
This results in:
$objProps
LN : 0
computer : $_.computer
connector_guid : $_.connector_guid
date : $_.date
detection_id : $_.detection_id
event_type : $_.event_type
event_type_id : $_.event_type_id
file : $_.file
group_guids : $_.group_guids
id : $_.id
severity : $_.severity
timestamp : $_.timestamp
timestamp_nanoseconds : $_.timestamp_nanoseconds
Then I try to re-use that new list:
$l=0
`$ampEvents | forEach-Object { New-Object psObject ``
`-Property @{$objProps}; ``
$l ++ } | Out-Gridview
but I get:
ParserError:
Line |
`2 | -Property @{$objProps}; ``
| ~
| Missing '=' operator after key in hash literal.
Anyone out there got another spin I can take with this?
Thanks in advance!