Home > Back-end >  PowerShell 5.1 Why are these 2 functions returning different types
PowerShell 5.1 Why are these 2 functions returning different types

Time:06-12

function Main {
    $result1 = DoWork1
    $result1.GetType()

    $result2 = DoWork2
    $result2.GetType()
}

function DoWork1 {
    $result1 = Invoke-Sqlcmd -Query "select top 1 * from customer" -ServerInstance "(localdb)\MSSQLLocalDB" -Database "Database1" -OutputAs DataTables
    #assign to variable then return
    return $result1
}

function DoWork2 {
    #return results without assigning to variable
    return Invoke-Sqlcmd -Query "select top 1 * from customer" -ServerInstance "(localdb)\MSSQLLocalDB" -Database "Database1" -OutputAs DataTables
}

Main

Here is the unexpected output:

IsPublic IsSerial Name                                     BaseType                                                                                    
-------- -------- ----                                     --------                                                                                    
True     False    DataRow                                  System.Object                                                                               
True     True     DataTable                                System.ComponentModel.MarshalByValueComponent   

                                        

CodePudding user response:

Using a similar example from the previous Q&A, to reproduce the same behavior:

function Invoke-SqlExample {
    $dtt = [System.Data.DataTable]::new()
    [void] $dtt.Columns.Add('Name')
    $row = $dtt.NewRow()
    $row.Name  = "Hello"
    $dtt.Rows.Add($row)
    , $dtt
}

function Main {
    $result1 = DoWork1
    $result1.GetType()

    $result2 = DoWork2
    $result2.GetType()
}

function DoWork1 {
    $result1 = Invoke-SqlExample
    [pscustomobject]@{
        Function = $MyInvocation.MyCommand.Name
        Type     = $result1.GetType().Name
    } | Out-Host
    return $result1

    # Immediate fixes:
    #     return , $result1
    #     Write-Output $result1 -NoEnumerate
    #     $PSCmdlet.WriteObject($result1, $false) !! Only if Advanced Function
}

function DoWork2 {
     return Invoke-SqlExample
}

Main

The output you would get from this is:

Function Type
-------- ----
DoWork1  DataTable


IsPublic IsSerial Name            BaseType
-------- -------- ----            --------
True     False    DataRow         System.Object
True     True     DataTable       System.ComponentModel.MarshalByValueComponent

We can see that the unrolling of the DataTable is only done when previously assigned to a variable, even though the variable ($result1 in DoWork1 is still of the type DataTable).

This could be explained as, DoWork2 happens in a single pipeline as opposed to DoWork1 which happens in two pipelines, first the output from Invoke-SqlExample is collected in a variable, and then emitted as output (this is where the unrolling is triggered). This is based on assumptions, and may not be entirely correct.

As iRon suggested in his helpful comment from the prior answer, an immediate fix to have DoWork1 return the DataTable instance untouched (unrolled), we can use the comma operator , which will wrap the DataTable instance in an array which is then lost during enumeration (output from the function) since it is an array of one element. The other alternative would be using Write-Output -NeEnumerate. As last alternative we can also use $PSCmdlet.WriteObject(..., $false) only if the function is an advanced one.


Adding a similar example that demonstrates the same behavior, this one was provided by mclayton in his helpful comment:

function test {
    , [Collections.Generic.List[string]]@(
        'hello'
        'world'
    )
}

(& { test }).GetType()          # => List`1
(& { $a = test; $a }).GetType() # => Object[]
  • Related