1

I have a PowerShell script that connects to a localhost API that returns a dictionary of "sessions" and whether they are active

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$response = Invoke-RestMethod 'https://localhost:44356/api/RDM/getSessions' -Method 'GET' -Headers $headers
$numOfUsers = Invoke-RestMethod 'https://localhost:44356/api/RDM/countSessions' -Method 'GET' -Headers $headers
for($x = 0; $x -lt $numOfUsers; $x++)
{
$user = $response.userId[$x]
$name = $response.sessionName[$x]
"`nSession Name: $name"
"User Logged In: $user`n"
}
pause

When more than one session is active it returns correctly:

Session Name: ExampleSession
User Logged In: ExampleUser

But when only 1 is active it returns only the first letter:

Session Name: E
User Logged In: E

I do know that this bug is caused by the script and not the API, however so far I am unable to find the source of the problem.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • this is one reason that i prefer to use the `foreach` loop structure instead of the `for` loop. the 1st handles things one at a time by its very nature ... [*grin*] – Lee_Dailey Feb 07 '21 at 15:17
  • @Lee_Dailey, true in general, but if you need to iterate over _multiple_ collections, `foreach` doesn't help. For a related discussion, see [GitHub proposal #14724](https://github.com/PowerShell/PowerShell/issues/14724). – mklement0 Feb 07 '21 at 22:49
  • that seems to be what `for` is for ... but i do agree that letting PoSh handle the nitty-gritty indexing stuff is an interesting thing. not sure it is worth it, tho. thank you for the link! [*grin*] – Lee_Dailey Feb 08 '21 at 00:46
  • @Lee_Dailey: Letting PowerShell handle the nitty-gritty of iteration is why you advocated for `foreach` here. I pointed out that, because in this case _multiple_ collections are to be iterated over, `foreach` is currently _not_ an option. [This comment](https://github.com/PowerShell/PowerShell/issues/14724#issuecomment-774709735) on the linked GitHub issue in particular suggests a way in which `foreach` could be enhanced to also cover this use case, using the _hypothetical_ (as of v7.1) syntax `foreach ($user, $name in $response.userId, $response.sessionName) { ... }` – mklement0 Feb 08 '21 at 01:54

1 Answers1

2

The implication is that if only one session is returned, $response.userId and $response.sessionName are not one-element arrays but just strings - and indexing into strings returns characters (e.g., 'foo'[0] returns 'f').[1]

You can use @(), the array-subexpression operator to ensure that you're always dealing with arrays:

$user = @($response.userId)[$x]
$name = @($response.sessionName)[$x]

If the count of sessions can be large, the following performs better (because @(...) creates a (shallow) copy of an array[2], whereas an [array] cast uses an existing array as-is):

$user = ([array] $response.userId)[$x]
$name = ([array] $response.sessionName)[$x]

[1] Note that, unlike in C#, the character returned is technically also a string ([string], System.String), not a [char] (System.Char) instance, because PowerShell itself doesn't use the [char] type.

[2] In older PowerShell versions (up to v5.0), this also applied to arrays with explicitly enumerated elements; e.g., @('foo', 'bar'), but this case has since been optimized to accommodate the widespread practice of using such expressions to define array literals - even though the @(...) isn't strictly needed ('foo', 'bar' alone will do).

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Could having a large number of sessions cause it to cease operating? As far as performance goes it should be okay but if it could cause issues I'll need to change it. –  Feb 07 '21 at 22:42
  • 1
    Performance aside, using `@(...)` with a large number of sessions would only be a problem if you ran out of memory, which seems unlikely. – mklement0 Feb 07 '21 at 22:44