I am making a PSObject from a json file
bar.json
{
"derp": {
"buzz": 42
},
"woot": {
"toot": 9000
}
}
I can make a PSCustomObject form the json using ConvertFrom-Json
$foo = Get-Content .\bar.json -Raw |ConvertFrom-Json
$foo.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
However if I try and splat multiple json files, I get an array
$foo = Get-Content .\*.json -Raw |ConvertFrom-Json
$foo.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False Object[] System.Array
In order to iterate over $foo, I need 2 different code paths depending on the object type.
Can I get a single object from multiple json files?
If not, how would I compress an array of objects into a single object?
I've tried to make a new object $bar that contains all the array items of $foo
$bar = new-object psobject
$bar | add-member -name $foo[0].psobject.properties.name -value $foo[0].'derp' -memberType NoteProperty
Update
Per Walter Mitty's request. If I load a single file and run $foo[0]
$foo = Get-Content .\bar.json -Raw |ConvertFrom-Json
$foo[0].gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
$foo[0]
derp woot
------------ ------------
#{Provisioners=System.Object[]; OS=windows; Size=; V... #{Provisioners=System.Object[]; OS=windows; Size=; V...
Solution
I initially implemented AP's answer, but later refactored it to use mklement0 answer.
While $allObjects is an array, it still allows me to reference values by name which is what I was looking for
$allObjects = #(
Get-ChildItem '.\foo' -Filter *.json -Recurse | Get-Content -Raw | ConvertFrom-Json
)
# iterating over nested objects inside an array is hard.
# make it easier by moving all array objects into 1 parent object where
# the 'key' is the name (opposed to AP's answer where filename = key)
$newObject = New-Object PSObject
foreach ($i in $allObjects) {
$i.psobject.members | ?{$_.Membertype -eq 'noteproperty'} |%{$newObject | add-member $_.Name $_.Value}
}
If all you want is an Array of JSON objects which are parsed from the different files:
$final = #{}
# Loop Thru All JSON Files, and add to $Final[<name>] = <JSON>
ls .\*.json | % {
$o = cat $_ -Raw | ConvertFrom-Json
$final[$_.name] = [PsCustomObject]$o
}
$final = [PsCustomObject]$final
This will produce a Name -> Data map for all your JSON files as a nested PSObject
AP.'s helpful answer is an elegant solution for creating a single top-level object ([pscustomobject] instance) that:
houses all JSON-converted objects as properties named for their input filenames.
E.g., $final would contain a bar.json property whose value is the object equivalent of the JSON string from the question.
A caveat is that with duplicate filenames the last file processed would "win".
See bottom for a usage note re Unix-style aliases ls and cat.
To get an array of all converted-from-JSON objects, the following is sufficient:
$allObjects = #(Get-ChildItem -Filter *.json | Get-Content -Raw | ConvertFrom-Json)
Note that if you only need a single directory's JSON files,
$allObjects = #(Get-Content -Path *.json -Raw | ConvertFrom-Json) works too.
Note the use of #(...), the array subexpression operator, which ensures that what is returned is treated as an array, even if only a single object is returned.
By default, or when you use $(...), the regular subexpression operator, PowerShell unwraps any (potentially nested) single-item collection and returns only the item itself; see Get-Help about_Operators.
A note on the use of Unix-style aliases ls and cat for PowerShell's Get-ChildItem and Get-Content cmdlets, respectively:
Now that PowerShell is cross-platform, these aliases only exist in the Windows edition of PowerShell, whereas a decision was made to omit them from PowerShell Core on Unix platforms, so as not to shadow the standard Unix utilities of the same name.
It is better to get into the habit of using PowerShell's own aliases, which follow prescribed naming conventions and do not conflict with platform-specific utilities.
E.g., gci can be used for Get-ChildItem, and gc for Get-Content
For the naming conventions behind PowerShell's own aliases, see the documentation.
Related
I'm trying to save values between instances of the script running.
I'm doing that by reading a text file at the beginning of the script, and then overwriting that file with the new values at the end of the script.
tracker.txt:
x=1
y=4
z=3
I'm reading the script using:
Get-Content "$Root\tracker.txt" | Foreach-Object{
$Position = $_.Split("=")
New-Variable -Name $Position[0] -Value $Position[1]
}
Unfortunately my $x $y and $z variables are being interpreted as a string instead of an integer.
Looking up New-Variable parameters, it doesn't seem like I can specify the value type.
Also I tried:
New-Variable -Name $Position[0] -Value [int]$Position[1]
And:
New-Variable -Name $Position[0] -Value ($Position[1] + 0)
But both did not work as expected.
How can I set these variables as an integer? I'm trying to use them later in a loop and that keeps failing because the variable can't be a string.
In order to save values, consider using Powershell's own object serialization. That is, Export-Clixml and Import-Clixml cmdlets. When an object is serialized, its contents are written on a file. In addition to values, data types are there too.
Handling multiple variables is easier, if those are stored in a collection such as a hash table. Like so,
# Save some values in a hash table
$myKeys =#{ "a" = 1; "b" = 2 }
$myKeys["a"]
1
# Check variable a's type. Int32 is as expected
$myKeys["a"].gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
# Serialize the hash table
Export-Clixml -Path keys.xml -InputObject $myKeys
# Create a new hash table by deserializing
$newKeys = Import-Clixml .\keys.xml
# Check contents
$newkeys["a"]
1
# Is the new a also an int32? Yes, it is
$newkeys["a"].gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
The keys.xml is, as expected an XML file. Check out its contents with, say, Notepad to see how the objects are stored. When working with complex objects, -Depth switch needs to be used. Otherwise, serialization saves only two levels of nesting, and that'll break complex objects.
I will use this:
(Get-Content tracker.txt) | foreach {
invoke-expression "[int]`$$($_.split("=")[0])=$($_.split("=")[1])"
}
It will create three variables with type int32.
To answer your specific question, you can force it like this.
#'
var1=1
var2=2
'# | out-file "$Root\tracker.txt"
Get-Content "$Root\tracker.txt" | Foreach-Object{
$Position = $_.Split("=")
New-Variable -Name $Position[0] -Value ([int]$Position[1])
}
$var1.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
However I really like this approach.
Create a template
$template = #'
{name*:var1}={[int]value:1}
{name*:var2}={[int]value:2}
'#
Now use ConvertFrom-String and assign to $data
#'
test1=1
test2=2
test3=3
test4=4
test5=5
'# | ConvertFrom-String -TemplateContent $template -OutVariable data
Let's create our variables now. Very easy to read.
$data | foreach {New-Variable -Name $_.name -Value $_.value}
Get-Variable test*
Name Value
---- -----
test1 1
test2 2
test3 3
test4 4
test5 5
And most importantly, they are int.
$test1.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
Putting it all together and pulling from a file
$template = #'
{name*:var1}={[int]value:1}
{name*:var2}={[int]value:2}
'#
Get-Content "$Root\tracker.txt" -raw |
ConvertFrom-String -TemplateContent $template |
foreach {New-Variable -Name $_.name -Value $_.value}
I'm confused how the $_ variable works in certain contexts of piping. In this example for backing up a Bitlocker key:
Get-BitlockerVolume | % {$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint}
This is how I read it in English:
Get all BitLockerVolume objects
For each BitLockerVolume object, pipe the KeyProtector fields forwards
Pipe KeyProtector objects forwards further for those with a RecoverPassword
Run the Backup-BitlockerKeyProtector, and supply the MountPoint
However, MountPoint is a field of the BitLockerVolume object, as shown here:
PS C:\Windows\system32> Get-BitLockerVolume | Get-Member | Where-Object {$_.Name -eq "MountPoint"}
TypeName: Microsoft.BitLocker.Structures.BitLockerVolume
Name MemberType Definition
---- ---------- ----------
MountPoint Property string MountPoint {get;}
So, for the entire block wrapped in brakcets { }, will the $_ variable ALWAYS be the same through any amount of piping? For example, the object we are piping forwards is changing. It's no longer a BitLockerVolume Object, but instead a KeyProtector object. So will the $_ always refer to the BitLockerVolume object in this case, or will it change further down the pipeline depending on different types of objects piped further through the chain?
So $_ is the info from the current pipe.
1,2 | %{
$_
}
response
1
2
while
1,2 | %{
"a","b" | %{
$_
}
}
response
a
b
a
b
We can see in the first that the output from %_ is from the last info given which is 1,2. While the next example still loops 1,2 but the output is from the pipe inside a,b.
There are ways around this by storing the first pipe information into a variable in the second pipe
1,2 | %{
$Num = $_
"a","b" | %{
$Num
}
}
which case the output is
1
1
2
2
In the example you gave lets look at it formated
Get-BitlockerVolume | % {
$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}
You have 2 different pipes. The First is getting 'BitlockerVolumevolume'.
The second starts with you sending the BitlockerVolume's KeyProtector.
Its like saying
For each Bitlocker volume, Get KeyProtector.
For each KeyProtector, Get me ones that have the member RecoveryPassword
Foreach KeyProtector with member RecoveryPassword, Backup Bitlocker Key Protector Using KeyProtector's Mountpoints
So on one final note I would also assume the example you gave wouldnt work.
What you might be looking for is this...
Get-BitlockerVolume | % {
$MountPoint = $_.MountPoint
$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_.KeyProtectorId
}
Let's expand the aliases and fill in the implied parameters. $_ can only be used inside script blocks '{ }' that are options to cmdlets. Just because you're in a pipe, doesn't mean you can use $_ . The $_ here belongs to Foreach-Object. Where-Object is using a comparison statement.
Get-BitlockerVolume | Foreach-Object -Process {
$_.KeyProtector | Where-Object -Property RecoveryPassword |
Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}
I know there are already good answers here, but I feel one important question was not addressed. The question of what happens to $_ throughout the Foreach-Object {} block when there is nesting. I am going use ArcSet's example since that was the answer selected.
1,2 | % {
"$_ before second foreach"
'a','b' | % {
"$_ inside second foreach"
}
"$_ after second foreach"
}
1 before second foreach
a inside second foreach
b inside second foreach
1 after second foreach
2 before second foreach
a inside second foreach
b inside second foreach
2 after second foreach
Notice that $_ becomes the current object being processed by the code within the Foreach-Object {} blocks. When entering the second Foreach-Object block, $_ changes. When exiting the second Foreach-Object block, $_ changes back to the object that will be continued to be processed by the remainder of the first block. So $_ neither remains the same nor is lost during the block processing. You will need to either assign $_ as another variable or in applicable situations use the -PipelineVariable switch to access those objects within different blocks.
Id' like to build on ArcSet's answer just a little bit. Since I finally understood the value of the $PSItem is changing when the type changes down the pipeline, I ran this code to do a little check.
Get-BitLockerVolume | % {$_.GetType()}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False BitLockerVolume System.Object
True False BitLockerVolume System.Object
True False BitLockerVolume System.Object
Here we can see some objects returned by the pipeline are of the BitLockerVolume type.
Now, based on my original question/example, if we pipe it further based on KeyProtector the object type will change for the $PSItem variable.
Get-BitLockerVolume | % { $_.KeyProtector | ? RecoveryPassword | % {$_.GetType()}}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False BitLockerVolumeKeyProtector System.Object
True False BitLockerVolumeKeyProtector System.Object
So at this point, at the end of the pipeline, we execute some other cmdlet like Backup-BitlockerKeyProtector and reference the $PSItem variable, aka $_, then it will refer to the object types last passed through the pipeline, in this case, the objects would be of the BitLockerVolumeKeyProtector type.
I know how to return hashtables, arrays etc from Powershell to c# through PSObjects. In order to use that, I need to return an object from the Powershell script - not just list the output.
But how can I get from a list in Powershell to something structured I can return from the Powershell script?
Think about this simple scenario (for example purposes):
Get-ChildItem C:\Test
And I get something like this output:
PS C:\test> Get-ChildItem
Directory: C:\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 03.06.2013 14:59 6 test1.txt
-a--- 03.06.2013 14:59 5 test2.txt
Now I want to take the Name and Length property of each file and return it from the powershell script as some sort of object, I can process with C#.
An example of something I could handle from C# would be this:
$a = New-Object psobject -Property #{
Name = "test1.txt"
Age = 6
}
$b = New-Object psobject -Property #{
Name = "test2.txt"
Age = 5
}
$myarray = #()
$myarray += $a
$myarray += $b
Return $myarray
How do I get from Get-ChildItem (or something similar that gives a list) to an array of objects or something similar?
Please note that this is not about Get-ChildItem specifically, it is just used as an example of something that outputs a list.
Something like that should do it:
$arr = Get-ChildItem | Select-Object Name,Length
return $arr
If for some reason it doesn't work, try nesting the array in a one array element (using the comma operator)
return ,$arr
Can anybody explain the details? If I create an object using
$var = [PSObject]#{a=1;b=2;c=3}
and then I look for its type using getType() PowerShell tells me it's of type Hashtable.
When using Get-Member (alias gm) to inspect the object it's obvious that a hashtable has been created, since it has a keys and a values property. So what's the difference to a "normal" hashtable?
Also, what's the advantage of using a PSCustomObject? When creating one using something like this
$var = [PSCustomObject]#{a=1;b=2;c=3}
the only visible difference to me is the different datatype of PSCustomObject. Also instead of keys and value properties, a inspection with gm shows that now every key has been added as a NoteProperty object.
But what advantages do I have? I'm able to access my values by using its keys, just like in the hashtable. I can store more than simple key-value pairs (key-object pairs for example) in the PSCustomObject, JUST as in the hashtable. So what's the advantage? Are there any important differences?
One scenario where [PSCustomObject] is used instead of HashTable is when you need a collection of them. The following is to illustrate the difference in how they are handled:
$Hash = 1..10 | %{ #{Name="Object $_" ; Index=$_ ; Squared = $_*$_} }
$Custom = 1..10 | %{[PSCustomObject] #{Name="Object $_" ; Index=$_ ; Squared = $_*$_} }
$Hash | Format-Table -AutoSize
$Custom | Format-Table -AutoSize
$Hash | Export-Csv .\Hash.csv -NoTypeInformation
$Custom | Export-Csv .\CustomObject.csv -NoTypeInformation
Format-Table will result in the following for $Hash:
Name Value
---- -----
Name Object 1
Squared 1
Index 1
Name Object 2
Squared 4
Index 2
Name Object 3
Squared 9
...
And the following for $CustomObject:
Name Index Squared
---- ----- -------
Object 1 1 1
Object 2 2 4
Object 3 3 9
Object 4 4 16
Object 5 5 25
...
The same thing happens with Export-Csv, thus the reason to use [PSCustomObject] instead of just plain HashTable.
Say I want to create a folder. If I use a PSObject you can tell it is wrong by
looking at it
PS > [PSObject] #{Path='foo'; Type='directory'}
Name Value
---- -----
Path foo
Type directory
However the PSCustomObject looks correct
PS > [PSCustomObject] #{Path='foo'; Type='directory'}
Path Type
---- ----
foo directory
I can then pipe the object
[PSCustomObject] #{Path='foo'; Type='directory'} | New-Item
From the PSObject documentation:
Wraps an object providing alternate views of the available members and ways to extend them. Members can be methods, properties, parameterized properties, etc.
In other words, a PSObject is an object that you can add methods and properties to after you've created it.
From the "About Hash Tables" documentation:
A hash table, also known as a dictionary or associative array, is a compact data structure that stores one or more key/value pairs.
...
Hash tables are frequently used because they are very efficient for finding and retrieving data.
You can use a PSObject like a Hashtable because PowerShell allows you to add properties to PSObjects, but you shouldn't do this because you'll lose access to Hashtable specific functionality, such as the Keys and Values properties. Also, there may be performance costs and additional memory usage.
The PowerShell documentation has the following information about PSCustomObject:
Serves as a placeholder BaseObject when PSObject's constructor with no parameters is used.
This was unclear to me, but a post on a PowerShell forum from the co-author of a number of PowerShell books seems more clear:
[PSCustomObject] is a type accelerator. It constructs a PSObject, but does so in a way that results in hash table keys becoming properties. PSCustomObject isn't an object type per se – it's a process shortcut. ... PSCustomObject is a placeholder that's used when PSObject is called with no constructor parameters.
Regarding your code, #{a=1;b=2;c=3} is a Hashtable. [PSObject]#{a=1;b=2;c=3} doesn't convert the Hashtable to a PSObject or generate an error. The object remains a Hashtable. However, [PSCustomObject]#{a=1;b=2;c=3} converts the Hashtable into a PSObject. I wasn't able to find documentation stating why this happens.
If you want to convert a Hashtable into an object in order to use its keys as property names you can use one of the following lines of code:
[PSCustomObject]#{a=1;b=2;c=3}
# OR
New-Object PSObject -Property #{a=1;b=2;c=3}
# NOTE: Both have the type PSCustomObject
If you want to convert a number of Hashtables into an object where their keys are property names you can use the following code:
#{name='a';num=1},#{name='b';num=2} |
% { [PSCustomObject]$_ }
# OR
#{name='a';num=1},#{name='b';num=2} |
% { New-Object PSObject -Property $_ }
<#
Outputs:
name num
---- ---
a 1
b 2
#>
Finding documentation regarding NoteProperty was difficult. In the Add-Member documentation, there isn't any -MemberType that makes sense for adding object properties other than NoteProperty. The Windows PowerShell Cookbook (3rd Edition) defined the Noteproperty Membertype as:
A property defined by the initial value you provide
Lee, H. (2013). Windows PowerShell Cookbook. O'Reilly Media, Inc. p. 895.
One advantage I think for PSObject is that you can create custom methods with it.
For example,
$o = New-Object PSObject -Property #{
"value"=9
}
Add-Member -MemberType ScriptMethod -Name "Sqrt" -Value {
echo "the square root of $($this.value) is $([Math]::Round([Math]::Sqrt($this.value),2))"
} -inputObject $o
$o.Sqrt()
You can use this to control the sorting order of the PSObject properties (see PSObject sorting)
I think the biggest difference you'll see is the performance. Have a look at this blog post:
Combining Objects Efficiently – Use a Hash Table to Index a Collection of Objects
The author ran the following code:
$numberofobjects = 1000
$objects = (0..$numberofobjects) |% {
New-Object psobject -Property #{'Name'="object$_";'Path'="Path$_"}
}
$lookupobjects = (0..$numberofobjects) | % {
New-Object psobject -Property #{'Path'="Path$_";'Share'="Share$_"}
}
$method1 = {
foreach ($object in $objects) {
$object | Add-Member NoteProperty -Name Share -Value ($lookupobjects | ?{$_.Path -eq $object.Path} | select -First 1 -ExpandProperty share)
}
}
Measure-Command $method1 | select totalseconds
$objects = (0..$numberofobjects) | % {
New-Object psobject -Property #{'Name'="object$_";'Path'="Path$_"}
}
$lookupobjects = (0..$numberofobjects) | % {
New-Object psobject -Property #{'Path'="Path$_";'Share'="Share$_"}
}
$method2 = {
$hash = #{}
foreach ($obj in $lookupobjects) {
$hash.($obj.Path) = $obj.share
}
foreach ($object in $objects) {
$object |Add-Member NoteProperty -Name Share -Value ($hash.($object.path)).share
}
}
Measure-Command $method2 | select totalseconds
Blog author's output:
TotalSeconds
------------
167.8825285
0.7459279
His comment regarding the code results is:
You can see the difference in speed when you put it all together. The object method takes 167 seconds on my computer while the hash table method will take under a second to build the hash table and then do the lookup.
Here are some of the other, more-subtle benefits:
Custom objects default display in PowerShell 3.0
We have a bunch of templates in our Windows-PKI and we needed a script, that has to work with all active templates. We do not need to dynamically add templates or remove them.
What for me works perfect (since it is also so "natural" to read) is the following:
$templates = #(
[PSCustomObject]#{Name = 'template1'; Oid = '1.1.1.1.1'}
[PSCustomObject]#{Name = 'template2'; Oid = '2.2.2.2.2'}
[PSCustomObject]#{Name = 'template3'; Oid = '3.3.3.3.3'}
[PSCustomObject]#{Name = 'template4'; Oid = '4.4.4.4.4'}
[PSCustomObject]#{Name = 'template5'; Oid = '5.5.5.5.5'}
)
foreach ($template in $templates)
{
Write-Output $template.Name $template.Oid
}
Type-1: $PSCustomObject = [PSCustomObject] #{a=1;b=2;c=3;d=4;e=5;f=6}
Type-2: $PsObject = New-Object -TypeName PSObject -Property #{a=1;b=2;c=3;d=4;e=5;f=6}
The only difference between Type-1 & Type-2
Type-1 Property are displayed in same order as we added
Type-1 enumerates the data faster
Type-1 will not work with systems running PSv2.0 or earlier
Both Type-1 & Type-2 are of type “System.Management.Automation.PSCustomObject”
Difference between HashTable and PSCustomObject/PSObject is
You can add new methods and properties to PSCustomObject/PSObject
You can use PSCustomObject/PSObject for pipeline parameter binding using ValueFromPipelineByPropertyName as explained by Zombo
example: [PSCustomObject] #{Path='foo'; Type='directory'} | New-Item
I've been working on some PowerShell functions to manage objects implemented in an assembly we have created. One of the classes I have been working with implements IEnumerable. Unfortunatly, this causes PowerShell to unroll the object at every opportunity. (I can't change the fact that the class implements IEnumerable.)
I've worked around the problem by creating a PSObject and copying the properties of our custom object to the PSObject, then returning that instead of the custom object. But I'd really rather return our custom object.
Is there some way, presumably using my types.ps1xml file, to hide the GetEnumerator() method of this class from PowerShell (or otherwise tell PowerShell to never unroll it).
Wrapping in a PSObject is probably the best way.
You could also explicitly wrap it in another collection—PowerShell only unwraps one level.
Also when writing a cmdlet in C#/VB/... when you call WriteObject use the overload that takes a second parameter: if false then PowerShell will not enumerate the object passed as the first parameter.
Check out the Write-Output replacement http://poshcode.org/2300 which has a -AsCollection parameter that lets you avoid unrolling. But basically, if you're writing a function that outputs a collection, and you don't want that collection unrolled, you need to use CmdletBinding and PSCmdlet:
function Get-Array {
[CmdletBinding()]
Param([Switch]$AsCollection)
[String[]]$data = "one","two","three","four"
if($AsCollection) {
$PSCmdlet.WriteObject($data,$false)
} else {
Write-Output $data
}
}
If you call that with -AsCollection you'll get very different results, although they'll LOOK THE SAME in the console.
C:\PS> Get-Array
one
two
three
four
C:\PS> Get-Array -AsCollection
one
two
three
four
C:\PS> Get-Array -AsCollection| % { $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String[] System.Array
C:\PS> Get-Array | % { $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
True True String System.Object
True True String System.Object
True True String System.Object
This may not directly answer the original question, since it technically isn't returning the custom object itself, but I had a similar problem where I was trying to display the properties of a custom type that implements IEnumerable and thought it was worth sharing. Piping this object into cmdlets like Get-Member and Select-Object always enumerates the contents of the collection. However, using those cmdlets directly and passing the object to the -InputObject parameter produces the expected results. Here, I'm using an ArrayList as an example, but $list can be an instance of your custom collection type:
# Get a reference to a collection instance
$list = [System.Collections.ArrayList]#(1,2,3)
# Display the properties of the collection (in this case, the ArrayList),
# not the items it contains (Int32)
Select-Object -InputObject $list -Property *
# List the members of the ArrayList class, not the contained items
Get-Member -InputObject $list
As other comments have mentioned, you can save the results of Select-Object as a variable, creating a new PSObject that you can use directly without enumerating the contents:
# Wrap the collection in a PSObject
$obj = Select-Object -InputObject $list -Property *
# Properties are displayed without enumerating contents
$obj
$obj | Format-List
$obj | Format-Table