2

I know I can add to an [Ordered] hash table at a particular index, with

$ordered.Insert([Int], [String], [String])

and I can set the value at a certain index with

$ordered[[Int]] =

And that leads me to THINK that I should also be able to GET the index of a given key. It seems that direct access isn't possible, as there in no .IndexOf() method. But I know I can get an array of the keys, and an array does have .IndexOf(), but that then returns another array, of -1 for the non matches and 0 for the matches. So

$ordered = [Ordered]@{
    'one' = 1
    'two' = 2
    'three' = 3
    'four' = 4
}
$ordered
$ordered.Keys.IndexOf('two')

produces

Name                           Value                                                                                                                                                                                                                        
----                           -----                                                                                                                                                                                                                        
one                            1                                                                                                                                                                                                                            
two                            2                                                                                                                                                                                                                            
three                          3                                                                                                                                                                                                                            
four                           4                                                                                                                                                                                                                            
-1
0
-1
-1

So now I need to get the index of that 0. So that doesn't seem to be the way to do it. I also found this. And indeed this works.

[Array]::IndexOf($ordered.Keys,'three')

But it has me wondering, is there a way to make the previous approach work? $ordered.Keys.IndexOf([String]) just seems like it should be viable, and I am just missing how to extract only the successful match. But perhaps not, and the static method is the way to go?

5
  • 2
    In PowerShell Core / .NET Core, OrderedDictionaryKeyValueCollection implements IList so, $ordered[$ordered.Keys.IndexOf('two')] works perfectly fine. In .NET Framework a bit more work is needed tho, I'm surprised you have a real use case for this. Why not explain what you're trying to accomplish as there is likely a better way to do it Commented Oct 1, 2022 at 19:44
  • 1
    It's a pretty edge case situation for sure. Related to my post earlier. I have an ordered hashtable of registry Uninstall keys, and some of those keys are actually extraneous Autodesk junk. Sometimes a redundant GUID key, sometimes a non GUID key, in both cases containing useless Uninstall info. So, I want to find the index of the correct item, and move (delete/add actually) the redundant key so it shows up in the log immediately after the correct one. As an aid in validating the results. It's... ugly. And FWIW, I am limited to PS 5.1. Commented Oct 1, 2022 at 20:00
  • Right I understand but, what is different doing (assuming this was possible for you in pwsh 5.1) $ordered[$ordered.Keys.IndexOf('two')] than doing just this $ordered['two'] or $ordered.ContainsKey('two') for validation ? Maybe If you can add a reproducible example of your problem we may be able to help you find the best solution to the problem, I know what you have right now is a reproducible example but I think the solution you're looking for is reinventing the wheel in this case Commented Oct 1, 2022 at 20:03
  • The key is I need to find the index of 'two' so I can insert something at that index +1. For example, the correct Uninstall data for Autodesk Civil 3D 2023 is found in the {AD211D5C-0BFF-3956-8998-C5C1F8FB5884} key and some rubbish data is found in {28B89EEF-6100-0409-2102-CF3F3A09B77D}. In my log I want the correct log info + X64 {AD211D5C-0BFF-3956-8998-C5C1F8FB5884}: Autodesk Civil 3D 2023 - English (13.5.211.0) [ADSK GUID ODIS (ADSK GUID NULL)] to be immediately followed by the bogus log item, despite the fact that it was actually processed first. Commented Oct 1, 2022 at 20:10
  • And I should say, the bogus item could be a GUID from earlier, or a GUID from later, or a non GUID key from later. Autodesk is ALL OVER the map as far as where info goes. So I process the keys in order, with conditionals that define the valid key and any key to skip, and if the skip has already been processed I can flag it, but if not I add the log item under its own path as the key, while also including the correct path. Then, when I later run across the path to skip, I can extract the correct path, find the index of the correct path, and insert the skip message right after. Fun. :) Commented Oct 1, 2022 at 20:17

2 Answers 2

2

In Windows PowerShell, use @(...) to convert the .Keys collection to a regular array, on which you can call .IndexOf():

@($ordered.Keys).IndexOf('two')

Note: Unlike direct, key-based access to your ordered hashtable, .IndexOf() is case-sensitive.


As Santiago Squarzon points out, the use of @(...) is no longer necessary in PowerShell (Core) 7.3+ (.NET 7+), where the collection type contained in the .Keys property value itself implements the IList interface and therefore directly supports .IndexOf().


Note:

  • The above also applies to accessing .Keys by index, e.g. @($ordered.Keys)[0] / $ordered.Keys[0]

  • As of this writing, the 7.3+ / .NET 7+ improvement will only apply to System.Collections.Specialized.OrderedDictionary, the type that PowerShell's [ordered] @{ ... } literal syntax creates instances of, and not also to other ordered or sorted dictionary types; GitHub issue #63537 aims to change that.


As for what happened in your attempt:

Since in Windows PowerShell the .Keys collection itself has no .IndexOf() method, member-access enumeration was performed, resulting in .IndexOf() calls - as a string method - on each key.

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

5 Comments

good lord, by now I should be used to coercing things to proper arrays. It never occurred to me to check the type of the keys collection. I just assumed it was an array. Doh!
@Gordon, fortunately this isn't a concern anymore in PS Core. As for what happened without @(...) in WinPS: since the .Keys collection itself has no .IndexOf() method, member-access enumeration was performed, resulting in .IndexOf() call - as a string method - on each key.
Yeah, someday I will remember that PowerShell will coerce things when I don't expect it, and have methods with the same name but totally different behavior. I am trying to refactor to use classes, which will help. But I need to be better at looking at what type I am working with as soon as I have problems.
What worries me though is that I want to work in VS Code on Mac, because, well, Windows sucks. And VS Code performance on a Parallels VM also kinda sucks. But that means I will be developing in PS Core but production code runs in PS Windows. I need to make sure I can force VS Code to actually run everything in the PS 5.1 engine so that I don't get caught out by a whole new host of issues. :)
That sounds challenging, @Gordon. You'd have to find a linter that checks PowerShell code for 5.1 compatibility, and I'd be surprised if one existed.
1

As an alternative to mklement0's helpful answer you could also extend the type itself adding a new PSScriptMethod to find index of a Key. This can be accomplished with Update-TypeData.

Update-TypeData -MemberType ScriptMethod -MemberName GetIndexOf -Value {
    param([object] $Key)

    return [array]::IndexOf($this.Keys, $Key)
} -TypeName System.Collections.Specialized.OrderedDictionary

$ordered.GetIndexOf('four') # => 3

1 Comment

I might not go this route for this particular issues, but good to know this is an option.

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.