Henrov
Henrov

Reputation: 1610

Powershell only outputting first character

I have the following dataset:

 id|selectedquery|
 1|SELECT fieldX FROM tableA|
 2|SELECT fieldY FROM tableB|

that dataset is used in the following code

$rows=($dataSet.Tables | Select-Object -Expand Rows)
$i=0
foreach ($row in $rows)
{
#Write-Output $rows.selectquery[$i].length
$query =   $rows.selectquery[$i]
#Write-Output $rows.selectquery[$i]
--doing some stuff--
$i++
}

Often $rows.selectquery[$i] only gives me the first character of the value in the field selectedquery being the 'S'.

When I remove the [$i] from $rows.selectquery it gives me (understandably) multiple records back. If I then put the [$i] back after $rows.selectquery[$i] things woerk fine.

Can anyone explain this behaviour?

Upvotes: 1

Views: 1177

Answers (2)

mklement0
mklement0

Reputation: 437608

Mathias' helpful answer shows the best way to solve your particular problem.

As for what happened:

You - inadvertently - used PowerShell's member-access enumeration feature when you used $rows.selectquery; that is, even though $rows is a collection that itself has no .selectquery property, PowerShell accessed that property on every element of the collection and returned the resulting values as an array.

The pitfall is that if the collection only has one element, the return value is not an array - it is just the one and only element's property value itself.

While this is analogous to how the pipeline operates (a single output object is captured by itself if assigned to a variable, while two or more are implicitly collected in an array), it is somewhat counterintuitive in the context of member-access enumeration:

  • In other words, $collection.SomeProperty is equivalent to $collection | ForEach-Object { $_.SomeProperty } and not, as would make more sense, because it always returns an array (collection), $collection.ForEach('SomeProperty')
  • GitHub issue #6802 discusses this problem.

While this behavior is often unproblematic, because PowerShell offers unified handling of scalars and collections (e.g. (42)[0], is the same as 42 itself; see this answer), a problem arises if the single value returned happens to be a string, because indexing into a string returns its characters.

  • Workaround: Cast to [array] before applying the index:
    • ([array] $rows.selectquery)[0]

A simple example:

# Multi-element array.
[array] $rows1 = [pscustomobject] @{ selectquery = 'foo' },
                 [pscustomobject] @{ selectquery = 'bar' }

# Single-element array:
[array] $rows2 = [pscustomobject] @{ selectquery = 'baz' }

# Contrast member-access enumeration + index access between the two:
[pscustomobject] @{
  MultiElement = $rows1.selectquery[0]
  SingleElement = $rows2.selectquery[0]
  SinglElementWithWorkaround = ([array] $rows2.selectquery)[0]
}

The above yields the following:

MultiElement SingleElement SinglElementWithWorkaround
------------ ------------- --------------------------
foo                      b baz

As you can see, the multi-element array worked as expected, because the member-access enumeration returned an array too, while the single-element array resulted in single string 'baz' being returned and 'baz'[0] returns its first character, 'b'.

Casting to [array] first avoids that problem (([array] $rows2.selectquery)[0]).

  • Using @(...), the array-subexpression operator - @($rows.selectquery)[0] - is another option, but, for the sake of efficiency, it should only be used on commands (e.g., @(Get-ChildItem -Name *.txt)[0]) not expressions, as in the case at hand.)

Upvotes: 1

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174485

You'll want to reference the SelectQuery property on either $row or $rows[$i] - not the entire $rows collection:

$row.SelectQuery
# or
$rows[$i].SelectQuery

Upvotes: 3

Related Questions