6

This should be very simple - I need to return an array of hashtables from a function. This works when there is more than one hashtable, but when there is only one then the result is not an array. I'd rather not test if the result is an array or not.

function GetArrayWith1Hashtable()
{
    $array = @()

    $hashtable = @{}
    $hashtable["a"] = "a" 
    $hashtable["b"] = "b" 
    $hashtable["c"] = "c"
    $array += $hashtable 

    Write-Host "GetArrayWith1Hashtable array.Length =" $array.Length
    Write-Host "GetArrayWith1Hashtable array.Count" $array.Count
    Write-Host "GetArrayWith1Hashtable array[0].Keys" $array[0].Keys

    $array
}

function GetArrayWith2Hashtables()
{
    $array = @()

    $hashtable = @{}
    $hashtable["a"] = "a" 
    $hashtable["b"] = "b" 
    $hashtable["c"] = "c"
    $array += $hashtable 

    $hashtable2 = @{}
    $hashtable2["d"] = "d" 
    $hashtable2["e"] = "e"
    $hashtable2["f"] = "f"
    $array += $hashtable2 

    Write-Host "GetArrayWith2Hashtables array.Length = " $array.Length
    Write-Host "GetArrayWith2Hashtables array.Count = " $array.Count
    Write-Host "GetArrayWith2Hashtables array[0].Keys =" $array[0].Keys
    Write-Host "GetArrayWith2Hashtables array.Count = "$array[1].Keys

    $array
}

$result1 = GetArrayWith1Hashtable
# $result1.Length - not available
Write-Host "Result of GetArrayWith1Hashtable result1.Count = " $result1.Count                      # Count = 2 (would expect this to be 1)
# $result1[0] not available - not an array

$result2 = GetArrayWith2Hashtables
Write-Host "Result of GetArrayWith2Hashtables result2.Length = "    $result2.Length # Length = 2
Write-Host "Result of GetArrayWith2Hashtables result2.Count = "     $result2.Count # Count = 2
Write-Host "Result of GetArrayWith2Hashtables result2[0].Keys = "     $result2[0].Keys # Keys = c a b
Write-Host "Result of GetArrayWith2Hashtables result2[1].Keys = "     $result2[1].Keys # Keys = d e f

 <#
  FULL OUTPUT:

 GetArrayWith1Hashtable array.Length = 1
 GetArrayWith1Hashtable array.Count 1
 GetArrayWith1Hashtable array[0].Keys c a b

 Result of GetArrayWith1Hashtable result1.Count =  2

 GetArrayWith2Hashtables array.Length =  2
 GetArrayWith2Hashtables array.Count =  2
 GetArrayWith2Hashtables array[0].Keys = c a b
 GetArrayWith2Hashtables array.Count =  d e f

 Result of GetArrayWith2Hashtables result2.Length =  2
 Result of GetArrayWith2Hashtables result2.Count =  2
 Result of GetArrayWith2Hashtables result2[0].Keys =  c a b
 Result of GetArrayWith2Hashtables result2[1].Keys =  d e f

 #>

3 Answers 3

9

Actually you would only add a comma in front of your returned item like so. This tells PowerShell to return it as is without trying to be smart about it. This also allows you to fix it in one place and not everywhere it was being called.

function GetArrayWith1Hashtable()
{
    $array = @()

    $hashtable = @{}
    $hashtable["a"] = "a" 
    $hashtable["b"] = "b" 
    $hashtable["c"] = "c"
    $array += $hashtable 

    Write-Host "GetArrayWith1Hashtable array.Length =" $array.Length
    Write-Host "GetArrayWith1Hashtable array.Count" $array.Count
    Write-Host "GetArrayWith1Hashtable array[0].Keys" $array[0].Keys

    # The only edit was to add the comma below
    ,$array
}

Update: Though, I don't think you are wanting this functionality, I will mention it so that others will understand the difference and make an educated choice for their approach.

To have your function be compatible to be used more efficiently within pipelines, the better approach would be to make a call to Write-Output for each item.

function GetArrayWith1Hashtable()
{
    Write-Output "a" 
    Write-Output "b" 
    Write-Output "c"
}

By using Write-Output, you are allowing PowerShell to call the next command's Process block in the pipeline, individually for each object. Otherwise it would call the next command and pass a single array object to it. You can read more about piping objects here

Sign up to request clarification or add additional context in comments.

5 Comments

Upvoted this answer because although the accepted answer works, this answer solves the problem more cleanly and the calling code doesn't require any modifications.
True, @PhilChuang, the caller doesn't require modification, but on the flip side a function that emits arrays as single objects won't work as expected in pipelines.
@mklement0 as I stated in my other response to you, you are assuming to much and not answering what the op asked. Not all functions are intended to be used in a pipe.
@JohnC: As stated in the comment on the other answer, your answer provides a solution for the question as asked (which is why I upvoted it), but the very point you just made deserves to be part of your answer: it's fine to use this approach for simple helper functions that aren't expected to be used in the pipeline. Equipped with that knowledge, readers can decide which approach is right for them.
The term 'Write-Object' is not recognized as the name of a cmdlet, function, script file, or operable program.
4

Just cast the return type to an array:

$result1 = @(GetArrayWith1Hashtable)

6 Comments

This is not casting, that's not how you cast to a type. What you are doing here is adding the results to an entirely new array, potentially adding a array to an array.
Indeed not a cast, @JohnC, but an application of @(), the array-subexpression operator. However, it is an effective solution for a function that emits its output objects one by one, as in the question, by virtue of PowerShell's automatic enumeration of output objects. It's arguably better to let the caller ensure that something is an array, as shown here, because a function that emits arrays as single objects will not work as expected in a pipeline.
@mklement0 You're assuming to much. Not all functions written are intended to be piped. The op wanted to return an array as a single object. This example, as i clearly stated already, could potentially return an array within a array. If they were to follow what you are referring to to work properly with pipelines, then all of these examples are incorrect and they should of just called write-object with each item rather than return an array all at once.
@JohnC: Yes, it could return an array within an array; that is, if you write a function that exhibits nonstandard behavior (returning an array as a single object). If that is your intent, then your solution is better - and then there's no need for also using @(). Yes, technically this answer doesn't do exactly what the OP asked for, but in the grand scheme of things it is better to avoid the nonstandard behavior; another simple solution with the original function definition would be to use [array] $result1 = GetArrayWith1Hashtable
@mklement0 there is no standard that says what you are forced to return from a function. Not all functions are designed to be used in a pipe.
|
0

To force a function to return an array (no matter the size of the collection), I do this:

return Write-Output $array -NoEnumerate

I think that -NoEnumerate switch helps make it more explicit what I'm meaning to do.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.