Reputation: 1610
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
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:
$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')
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.
[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]
).
@(...)
, 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
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