1
votes

The following shows that the returned type are different depends on how many rows returned. Why it's designed this way? It's very easy to make assumption it always returns an array and write the code $a.Length $a | % { ....} which will raise error when it returns only one row, unless the it's written as $a = @(invoke-....), which is easy to forget.

$a=Invoke-Sqlcmd -ServerInstance server "select 1 a"
$b=Invoke-Sqlcmd -ServerInstance server "select 1 a union all select 2"
$a.GetType()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    DataRow                                  System.Object

And the following statement returns an array of object (BTW, why not an array of DataRow?)

$b.GetType()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     True     Object[]                                 System.Array

However, gm returns the same type for both variables. Why it's designed this way which can be very confused.

Question:

  1. What's the point that the array is removed when only one item is returned?
  2. Why gm get item type of an array? How to gm of an array?
  3. Why getType() cannot return the data type of the item when it returns an array type?

?

PS SQLSERVER:\> $a|gm


   TypeName: System.Data.DataRow

Name              MemberType            Definition
----              ----------            ----------
AcceptChanges     Method                void AcceptChanges()
......
ToString          Method                string ToString()
Item              ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a                 Property              int a {get;set;}


PS SQLSERVER:\> $b|gm


   TypeName: System.Data.DataRow

Name              MemberType            Definition
----              ----------            ----------
AcceptChanges     Method                void AcceptChanges()
......
ToString          Method                string ToString()
Item              ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a                 Property              int a {get;set;}
1
I wish I could answer this but I think I missing some key peices. 1. That might be up to Invoke-Sqlcmd and also depends on your PowerShell version. 2. Get-Member is acting on elements passed in the pipe not the entire object which is why 3. returns what it does.Matt
What version of powershell are you using?briantist

1 Answers

2
votes
  1. Most of the time in PowerShell, functions/cmdlets that return objects, simply return the object. If more than 1 object is to be returned, the function just keeps returning objects until it's done. PowerShell handles all of the returned objects and gives you an array.
  2. gm is an alias for Get-Member. When you call it by piping to it $a | gm, you are invoking it as a pipeline. In this case, each object in $a is individually passed to Get-Member, so it returns the type of the individual object(s). If they are all the same, then it will only display it once, but it's actually checking all of them. You can prevent this by calling Get-Member -InputObject $a which should show you the array type if it is an array.
  3. Similar to the above, .GetType() gets the type of whatever object it's invoked on, so if it's an array, then it returns that; it's not looking at the individual elements.

I also want to point out that % (ForEach-Object) and foreach() work fine when not used on an array: "hello" | % { $_ }, as does foreach($msg in "hello") { $msg }.

To address the issue of $a.Length not working when the return value is a single object, it depends on your powershell version.

In version 2, you will see the behavior you are seeing: you'll have to wrap it in an array first, or test for an array with something like:

if ($a -is [Array]) {
    $itemCount = $a.Length
} else {
    $itemCount = 1
}

But, in powershell 3+, you can do $a.Length even if $a is just some object and not an array. It will return 1. Length may not show up in autocomplete or in intellisense, but it will work.