If you remove the array subexpression @(...) and just precede with a comma. The below code seems to work:

Copyfunction TestReturn {
    $returnList = New-Object System.Collections.Generic.List[string]
    $returnList.Add('Testing, one, two')

    return , $returnList
}

$testList = TestReturn
$testList.GetType().FullName

Note: technically this causes the return of [Object[]] with a single element that's of type [System.Collections.Generic.List[string]]. But again because of the implicit unrolling it sort of tricks PowerShell into typing as desired.

On your later point, the syntax [Type]$Var type constrains the variable. It's basically locking in the type for that variable. As such subsequent calls to .GetType() will return that type.

These issues are due to how PowerShell implicitly unrolls arrays on output. The typical solution, somewhat depending on the typing, is to precede the return with a , or ensure the array on the call side, either by type constraining the variable as shown in your questions, or wrapping or casting the return itself. The latter might look something like:

Copy$testList = [System.Collections.Generic.List[string]]TestReturn
$testList.GetType().FullName

To ensure an array when a scalar return is possible and assuming you haven't preceded the return statement with , , you can use the array subexpression on the call side:

Copy$testList = @( TestReturn )
$testList.GetType().FullName

I believe this answer deals with a similar issue

Answer from Steven on Stack Overflow
🌐
Microsoft Learn
learn.microsoft.com › en-us › answers › questions › 327557 › list(t)-in-powershell
List<T> in PowerShell? - Microsoft Q&A
March 23, 2021 - Unless you're doing interop with a .NET type that requires it then just stick with PS arrays. [System.Collections.Generic.List[Errors]] $errors = @() # Or $errors = New-Object System.Collections.Generic.List[Errors]
🌐
GitHub
github.com › MicrosoftDocs › PowerShell-Docs › issues › 9997
Recommend `List<T>` instead of `ArrayList` in Performance Considerations doc · Issue #9997 · MicrosoftDocs/PowerShell-Docs
April 10, 2023 - CollectionSize Test TotalMilliseconds RelativeSpeed -------------- ---- ----------------- ------------- 5000 PowerShell Explicit Assignment 0.56 1x 5000 .Add(..) to List<T> 7.56 13.5x 5000 += Operator to Array 1357.74 2424.54x 10000 PowerShell Explicit Assignment 0.77 1x 10000 .Add(..) to List<T> 18.20 23.64x 10000 += Operator to Array 5411.23 7027.57x 25000 PowerShell Explicit Assignment 1.39 1x 25000 .Add(..) to List<T> 47.14 33.91x 25000 += Operator to Array 26168.67 18826.38x 50000 PowerShell Explicit Assignment 3.49 1x 50000 .Add(..) to List<T> 97.38 27.9x 50000 += Operator to Array 129537.09 37116.64x 75000 PowerShell Explicit Assignment 14.59 1x 75000 .Add(..) to List<T> 243.47 16.69x 75000 += Operator to Array 247419.68 16958.17x 100000 PowerShell Explicit Assignment 14.85 1x 100000 .Add(..) to List<T> 177.13 11.93x 100000 += Operator to Array 473824.71 31907.39x
Author   MicrosoftDocs
Discussions

powershell - [System.Collections.Generic.List[string]] as return value - Stack Overflow
2 Custom type in Powershell with Generics Collection as a member · 1 Powershell Error When Returning Collections.Generic.List[String] More on stackoverflow.com
🌐 stackoverflow.com
PowerShell should support creating an List similar to how it supports arrays
Then you can add an element to the array with $array += 4, but this creates a new array which is not performant. Powershell should have a syntax which allows creating lists. More on github.com
🌐 github.com
68
December 6, 2017
System.Collections.Generic.List[PSObject]
That's a great terse way to read ... generic list. I wonder if it will work with a hashset? Probably I'm guessing? Thanks for the experimentation even though your post is a bit behind schedule... I'll definitely be using that syntax for reading content from external files in many cases. ... PowerShell assumes that ... More on reddit.com
🌐 r/PowerShell
11
8
October 4, 2018
Powershell Array list
I am looking for help in arrays and forloop for a powershell script. I have list of domains in first array I need to reiterate get-computer command and give searchbase from my second array. The first index in domains list is the input for searchbase . We have 10 domains which I need to run ... More on forums.powershell.org
🌐 forums.powershell.org
4
0
April 27, 2020
🌐
Vexx32
vexx32.github.io › 2020 › 02 › 15 › Building-Arrays-Collections
Building Arrays and Collections in PowerShell
While the .NET documentation frequently uses the C# syntax List<T>, when we get to PowerShell we actually use square brackets to indicate the generic type parameter instead. List<T> instead becomes [List[T]], and in practical usage T is replaced with another type name.
🌐
IDERA
idera.com › home › using efficient lists in powershell
Using Efficient Lists in PowerShell | IDERA
April 22, 2025 - Learn how to handle dynamic lists efficiently in PowerShell using System. Collections.ArrayList and generic lists instead of default object.
Top answer
1 of 2
12

If you remove the array subexpression @(...) and just precede with a comma. The below code seems to work:

Copyfunction TestReturn {
    $returnList = New-Object System.Collections.Generic.List[string]
    $returnList.Add('Testing, one, two')

    return , $returnList
}

$testList = TestReturn
$testList.GetType().FullName

Note: technically this causes the return of [Object[]] with a single element that's of type [System.Collections.Generic.List[string]]. But again because of the implicit unrolling it sort of tricks PowerShell into typing as desired.

On your later point, the syntax [Type]$Var type constrains the variable. It's basically locking in the type for that variable. As such subsequent calls to .GetType() will return that type.

These issues are due to how PowerShell implicitly unrolls arrays on output. The typical solution, somewhat depending on the typing, is to precede the return with a , or ensure the array on the call side, either by type constraining the variable as shown in your questions, or wrapping or casting the return itself. The latter might look something like:

Copy$testList = [System.Collections.Generic.List[string]]TestReturn
$testList.GetType().FullName

To ensure an array when a scalar return is possible and assuming you haven't preceded the return statement with , , you can use the array subexpression on the call side:

Copy$testList = @( TestReturn )
$testList.GetType().FullName

I believe this answer deals with a similar issue

2 of 2
4

In addition to Steven's very helpful answer, you also have the option to use the [CmdletBinding()] attribute and then just call $PSCmdlet.WriteObject. By default it will preserve the type.

Copyfunction Test-ListOutput {
    [CmdletBinding()]
    Param ()
    Process {
        $List = New-Object -TypeName System.Collections.Generic.List[System.String]
        $List.Add("This is a string")
        $PSCmdlet.WriteObject($List)
    }
}
$List = Test-ListOutput
$List.GetType()
$List.GetType().FullName

For an array, you should specify the type.

Copyfunction Test-ArrayOutput {
    [CmdletBinding()]
    Param ()
    Process {
        [Int32[]]$IntArray = 1..5
        $PSCmdlet.WriteObject($IntArray)
    }
}
$Arr = Test-ArrayOutput
$Arr.GetType()
$Arr.GetType().FullName

By default the behaviour of PSCmdlet.WriteObject() is to not enumerate the collection ($false). If you set the value to $true you can see the behaviour in the Pipeline.

Copyfunction Test-ListOutput {
    [CmdletBinding()]
    Param ()
    Process {
        $List = New-Object -TypeName System.Collections.Generic.List[System.String]
        $List.Add("This is a string")
        $List.Add("This is another string")
        $List.Add("This is the final string")
        $PSCmdlet.WriteObject($List, $true)
    }
}

Test-ListOutput | % { $_.GetType() }

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object
True     True     String                                   System.Object
True     True     String                                   System.Object

(Test-ListOutput).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


# No boolean set defaults to $false
function Test-ListOutput {
    [CmdletBinding()]
    Param ()
    Process {
        $List = New-Object -TypeName System.Collections.Generic.List[System.String]
        $List.Add("This is a string")
        $List.Add("This is another string")
        $List.Add("This is the final string")
        $PSCmdlet.WriteObject($List)
    }
}

Test-ListOutput | % { $_.GetType() }

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     List`1                                   System.Object

(Test-ListOutput).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     List`1                                   System.Object

Just wanted to add some information on what I usually use in functions and how to control the behaviour.

🌐
PowerShell Gallery
powershellgallery.com › packages › ListFunctions › 1.1 › Content › ListFunctions.psm1
PowerShell Gallery | ListFunctions.psm1 1.1
This site uses cookies for analytics, personalized content and ads. By continuing to browse this site, you agree to this use.
🌐
Wit IT
witit.blog › easily-turn-a-list-into-an-array-within-powershell
Easily Turn a List into an Array Within PowerShell - Wit IT - witit
December 21, 2024 - I’ve been calling them CSVs, but once you import a CSV in PowerShell and store it in a variable it’s actually stored as an array. This is really what we want. We can see this by using the method .GetType() on a variable storing our imported CSV. So what we’re actually trying to do here is easily take a 1 column list someone provided for us (maybe a list of users from an email or from a CSV/XLSX file) and turn it into something usable (an array) so we can work our magic in PowerShell.
Find elsewhere
🌐
GitHub
github.com › PowerShell › PowerShell › issues › 5643
PowerShell should support creating an List similar to how it supports arrays · Issue #5643 · PowerShell/PowerShell
December 6, 2017 - Assuming the operator is @[...], you could create a list with $list = @['a', 1, '3'] and then you could add an element to the existing list with $list += 4 without PowerShell having to create a new list.
Author   PowerShell
🌐
Reddit
reddit.com › r/powershell › system.collections.generic.list[psobject]
r/PowerShell on Reddit: System.Collections.Generic.List[PSObject]
October 4, 2018 - That's a great terse way to read a file line by line while pushing it into a generic list. I wonder if it will work with a hashset? Probably I'm guessing? Thanks for the experimentation even though your post is a bit behind schedule... I'll definitely be using that syntax for reading content from external files in many cases. ... PowerShell assumes that when a mention takes a object (like List<object>.Add) that it should unwrap the psobject wrapper it is in.
🌐
PowerShell Forums
forums.powershell.org › powershell help
Powershell Array list - PowerShell Help - PowerShell Forums
April 27, 2020 - I am looking for help in arrays and forloop for a powershell script. I have list of domains in first array I need to reiterate get-computer command and give searchbase from my second array. The first index in domains li…
🌐
Varonis
varonis.com › blog › powershell-array
PowerShell Array Guide: How to Use and Create
June 9, 2022 - However, arrays can also contain objects, and many of the most common uses of PowerShell – like configuring Office 365 – require that you know how to work with objects. So let’s look at some basic commands for doing that. We can create an array of objects in the same way that we did with strings, using the @() function. For instance, to make a test list of employees, we can use:
🌐
Reddit
reddit.com › r/powershell › when to use array vs array list vs list
r/PowerShell on Reddit: When to use array vs array list vs list
April 25, 2022 - I never remember how to write the generic list type, and how do I use it for any type (using psojbect type?) ?. ... Yeah, I've done the same thing, especially when writing ad hoc blocks of code. However, if you're using Powershell v7, you can type [List and hit Ctrl+Space at the command line and it'll populate it for you.
🌐
Microsoft Learn
learn.microsoft.com › en-us › powershell › scripting › learn › deep-dives › everything-about-arrays
Everything you wanted to know about arrays - PowerShell | Microsoft Learn
Because arrays are such a basic feature of PowerShell, there is a simple syntax for working with them in PowerShell. ... We can create an array and seed it with values just by placing them in the @() parentheses. PS> $data = @('Zero','One','Two','Three') PS> $data.Count 4 PS> $data Zero One Two Three · This array has 4 items. When we call the $data variable, we see the list of our items.
🌐
O'Reilly
oreilly.com › library › view › windows-powershell-cookbook › 9781449359195 › ch07.html
7. Lists, Arrays, and Hashtables - Windows PowerShell Cookbook, 3rd Edition [Book]
January 10, 2013 - PowerShell makes working with arrays and lists much like working with other data types: you can easily create an array or list and then add or remove elements from it. You can just as easily sort it, search it, or combine it with another array.
Author   Lee Holmes
Published   2013
Pages   1034
🌐
EDUCBA
educba.com › home › data science › data science tutorials › powershell tutorial › powershell list
PowerShell list | Methods and examples of PowerShell list
March 10, 2023 - This article will cover in detail the list and its implementation along with appropriate examples. ... Valuation, Hadoop, Excel, Mobile Apps, Web Development & many more. ... Write-Host "Demo of list in Powershell" $testlist = New-Object -TypeName 'System.Collections.ArrayList' Write-Host "Datatype is " $testlist.GetType() -ForegroundColor Green $testlist1 = [System.Collections.ArrayList]::new() Write-Host "Datatype is " $testlist1.GetType() -ForegroundColor Green Write-Host "Declaring an empty list" [System.Collections.ArrayList]$testlist2= @() Write-Host "Datatype is " $testlist2.GetType() -ForegroundColor Green
Address   Unit no. 202, Jay Antariksh Bldg, Makwana Road, Marol, Andheri (East),, 400059, Mumbai
Top answer
1 of 1
1

I think Group-Object (alias: group) is the perative cmdlet for this task.

Here's one way:

$SourceList = @('PONTEVEDRA:false','MADRID:true','MADRID:true','PONTEVEDRA:true')
$NewList = $SourceList | ForEach{
    $_ -match '(.+):(.+)' | out-null
    [PSCustomObject]@{
        'Loc' = $matches[1]
        'Bool'= $matches[2]
    }
} | Group Loc -pv loc | ForEach{
    $BoolSplit = $_.Group | Group Bool -NoElement
    '{0}:{1}:{2}' -f $loc.Name,
                     ($BoolSplit | ? Name -eq 'True').Count,
                     ($BoolSplit | ? Name -eq 'False').Count
}

$NewList

Edit: Including summary information

If you break down the above code to see what's in the pipeline, you'll see that this code segment:

$SourceList | ForEach{
    $_ -match '(.+):(.+)' | out-null
    [PSCustomObject]@{
        'Loc' = $matches[1]
        'Bool'= $matches[2]
    }
}

Creates these objects:

Loc        Bool
---        ----
PONTEVEDRA false
MADRID     true
MADRID     true
PONTEVEDRA true

which, if grouped by Bool, will get the total counts of "True" and "False":

PS > $SourceList | ForEach{
>>     $_ -match '(.+):(.+)' | out-null
>>     [PSCustomObject]@{
>>         'Loc' = $matches[1]
>>         'Bool'= $matches[2]
>>     }
>> } | Group Bool

Count Name                      Group
----- ----                      -----
    1 false                     {@{Loc=PONTEVEDRA; Bool=false}}
    3 true                      {@{Loc=MADRID; Bool=true}, @{Loc=MADRID; Bool=true}, ...

So to capture the intermediate object, modify the original code to create another collection. This segment:

$NewList = $SourceList | ForEach{
    $_ -match '(.+):(.+)' | out-null
    [PSCustomObject]@{
        'Loc' = $matches[1]
        'Bool'= $matches[2]
    }
}

becomes this:

$NewList = ( $SumList = $SourceList | ForEach{
    $_ -match '(.+):(.+)' | out-null
    [PSCustomObject]@{
        'Loc' = $matches[1]
        'Bool'= $matches[2]
    }
} )

Then, when you run the modified code, you've not only created $NewList, but $SumList as well. With that, you group and select:

PS > $sumList | Group Bool -NoElement | select Name, Count

Name  Count
----  -----
false     1
true      3

So, the full modified code would be:

$SourceList = @('PONTEVEDRA:false','MADRID:true','MADRID:true','PONTEVEDRA:true')
$NewList = ( $SumList = $SourceList | ForEach{
    $_ -match '(.+):(.+)' | out-null
    [PSCustomObject]@{
        'Loc' = $matches[1]
        'Bool'= $matches[2]
    }
} ) | Group Loc -pv loc | ForEach{
    $BoolSplit = $_.Group | Group Bool -NoElement
    '{0}:{1}:{2}' -f $loc.Name,
                     ($BoolSplit | ? Name -eq 'True').Count,
                     ($BoolSplit | ? Name -eq 'False').Count
}

$NewList

$sumList | Group Bool -NoElement | select Name, Count

Edit3: Alternate summary method:

While the above would be good if you needed further access to your original data in object form, but if all you need are the sums mentioned, the following would probably more effecient and faster. It doesn't create another collection or list, but rather uses to refernce variables to keep a running total as the data is processed:

$SourceList = @('PONTEVEDRA:false','MADRID:true','MADRID:true','PONTEVEDRA:true')

[ref]$TrueTotal  = 0
[ref]$FalseTotal = 0

$NewList = $SourceList | ForEach{
    $_ -match '(.+):(.+)' | out-null
    [PSCustomObject]@{
        'Loc' = $matches[1]
        'Bool'= $matches[2]
    }
} | Group Loc -pv loc | ForEach{
    $BoolSplit = $_.Group | Group Bool -NoElement
    '{0}:{1}:{2}' -f $loc.Name,
                     ($TrueCount  = ($BoolSplit | ? Name -eq 'True').Count),
                     ($FalseCount = ($BoolSplit | ? Name -eq 'False').Count)
    $TrueTotal.Value  += $TrueCount
    $FalseTotal.Value += $FalseCount
}

$NewList
'Total "True"  : {0}' -f $TrueTotal.Value
'Total "False" : {0}' -f $FalseTotal.Value`



Edit #2: Additional data

Same as before, the first thing you want to do is convert your data into objects. I took a slightly different tack this time -- not to confuse, but to show an alternate method. If your data is actually coming from a file, you would use Import-Csv rather than ConvertFrom-Csv:

$list = @('PONTEVEDRA:false:siAPP','MADRID:true:noAPP','MADRID:true:noAPP','PONTEVEDRA:true:siAPP')

$ListObjects = $List | ConvertFrom-Csv -Delimiter ':' -Header ('Loc','locBool','AppInfo')

Which yields:

PS > $ListObjects

Loc        locBool AppInfo
---        ------- -------
PONTEVEDRA false   siAPP
MADRID     true    noAPP
MADRID     true    noAPP
PONTEVEDRA true    siAPP

And again, group on **Loc** :

PS > $ListObjects | group Loc

Count Name                      Group
----- ----                      -----
    2 PONTEVEDRA                {@{Loc=PONTEVEDRA; locBool=false; AppInfo=siAPP; Count=2; Na...
    2 MADRID                    {@{Loc=MADRID; locBool=true; AppInfo=noAPP; Count=2; Name=MA...

And with the Group property of each of those, we can extract the data you want using the Where method available to collections:

$ListObjects | Group Loc | %{
    $BoolGroup = $_.Group | Group locBool -NoElement
    $AppGroup  = $_.Group | Group AppInfo -NoElement
    '{0}:{1}:{2}:{3}:{4}' -f $_.Name ,
                             $BoolGroup.Where{$_.Name -match 'true'}[0].Count ,
                             $BoolGroup.Where{$_.Name -match 'false'}[0].Count ,
                             $AppGroup.Where{$_.Name -match 'siAPP'}[0].Count ,
                             $AppGroup.Where{$_.Name -match 'noApp'}[0].Count
}

Output:

PS > $ListObjects | Group Loc | %{
>>     $BoolGroup = $_.Group | Group locBool -NoElement
>>     $AppGroup  = $_.Group | Group AppInfo -NoElement
>>     '{0}:{1}:{2}:{3}:{4}' -f $_.Name ,
>>                              $BoolGroup.Where{$_.Name -match 'true'}[0].Count ,
>>                              $BoolGroup.Where{$_.Name -match 'false'}[0].Count ,
>>                              $AppGroup.Where{$_.Name -match 'siAPP'}[0].Count ,
>>                              $AppGroup.Where{$_.Name -match 'noApp'}[0].Count
>> }
>>
PONTEVEDRA:1:1:2:0
MADRID:2:0:0:2
PS >

Note that becaue the Where() method returns a collection even when there is zero or one element -- and because collections have a Count property of their own, you have to specify the [0] array index to get the Count from the BoolGroup and AppGroup sub-groups. Without that, Count would only return 0 or 1 -- depending on whether or not therewere any elements in the specified grouping.

🌐
O'Reilly
oreilly.com › library › view › mastering-windows-powershell › 9781787126305 › 165c2eb9-9944-4bb5-86d7-d6d8a76d0c0f.xhtml
Selecting elements from the list - Mastering Windows PowerShell Scripting - Second Edition [Book]
October 27, 2017 - Content preview from Mastering Windows PowerShell Scripting - Second Edition · As with the array, elements may be selected by index: $list = New-Object System.Collections.Generic.List[String] $list.AddRange([String[]]("Tom", "Richard", "Harry")) $list[1] # Returns Richard ·
Authors   Chris DentBrenton J.W. Blawat
Published   2017
Pages   440