81

I have this PSObject (from XML):

bool : {IsActive, ShowOnB2C, ShowOnB2B, IsWebNews}
str  : {id, ProductId, GroupName, Unit...}
int  : {ProductIdNumeric, Prices_SalesOne, Prices_Treater, Prices_B2C...}
arr  : {EnvironmentBrands, Catalogs, NavisionLevels}
long : long

I would like to iterate over the properties without using the property names, for instance bool.

I have tried to index into the object like this:

$document[0]

But that gives me nothing, but it does not cause any errors either.

Select-Object kind of works, but then I have to use the property names, and I don't want that.

$documents | Select-Object bool,str

ForEach do not iterate the properties.

$documents | ForEach {
    $_.name
}

returns doc and that is the name of the tag (XML) holding the bools, ints, and strings.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user1359448
  • 1,539
  • 3
  • 14
  • 23

5 Answers5

148

This is possible using the hidden property PSObject:

$documents.PSObject.Properties | ForEach-Object {
    $_.Name
    $_.Value
}

This won't work with certain PowerShell-created objects (PSObjects) that contain "NoteProperties" (properties of type NoteProperty).

See this answer for a method that covers all property types.

briantist
  • 45,546
  • 6
  • 82
  • 127
  • 1
    $documents.PSObject.Properties | ForEach-Object iterates the original object although $documents is a subset of the original XML object `$documents = $responseContent.ChildNodes.result.doc` – user1359448 Jun 08 '16 at 19:09
  • 1
    This won't work for PSCustomObjects that have NoteProperty members instead of Property members like the object output from a cmdlet. You have to use the get-member syntax shown in the other answer below to cover all variations of object properties. – Blaisem Nov 09 '21 at 22:25
  • 1
    Thanks @Blaisem I've updated this answer with a link to the other. – briantist Nov 10 '21 at 13:25
  • Thank you, the hidden property was killing me. – Eric Nord Oct 10 '22 at 23:03
  • The `.PSObject` is an intrinsic member and is documented here: [about_intrinsic_members](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_intrinsic_members?view=powershell-7.3) – Ole Jul 10 '23 at 09:29
28

I prefer using foreach to loop through PowerShell objects:

foreach($object_properties in $obj.PsObject.Properties)
{
    # Access the name of the property
    $object_properties.Name

    # Access the value of the property
    $object_properties.Value
}

Generally, foreach has higher performance than Foreach-Object.

And yes, foreach is actually different than Foreach-Object under the hood.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kellen Stuart
  • 7,775
  • 7
  • 59
  • 82
14

You might need NoteProperty too with Get-Member.

$documents | Get-Member -membertype property,noteproperty | 
  Foreach name

EDIT: type "properties" seems a more generic catchall

$documents | get-member -type properties | % name

EDIT: dump out all the values:

$obj = ls test.ps1
$obj | Get-Member -Type properties | foreach name | 
  foreach { $_ + ' = ' + $obj.$_ }

Attributes = Normal
CreationTime = 06/01/2019 11:29:03
CreationTimeUtc = 06/01/2019 15:29:03
Directory = /Users/js
DirectoryName = /Users/js
Exists = True
Extension = .ps1
FullName = /Users/js/test.ps1
IsReadOnly = False
LastAccessTime = 06/05/2019 23:19:01
LastAccessTimeUtc = 06/06/2019 03:19:01
LastWriteTime = 06/01/2019 11:29:03
LastWriteTimeUtc = 06/01/2019 15:29:03
Length = 55
Name = test.ps1

An alternative way without "| foreach name", that needs extra parentheses:

$obj | Get-Member -Type properties | 
  foreach { $_.name + ' = ' + $obj.($_.name) }

Another take which results in an array that's easier to work with, and might be good for objects converted from json:

$a = '{ prop1:1, prop2:2, prop3:3 }' | convertfrom-json     
$a

prop1 prop2 prop3
----- ----- -----
    1     2     3

$a.PSObject.Properties | select name,value

name  value
----  -----
prop1     1
prop2     2
prop3     3
js2010
  • 23,033
  • 6
  • 64
  • 66
  • 1
    This is the general answer. The ``$object.psobject.properties`` only works with objects output from a cmdlet. PSCustomObjects use NoteProperties and will require the ``| gm -MemberType NoteProperty`` syntax, or ``-MemberType properties`` which will work for objects with either properties or noteproperties. – Blaisem Nov 09 '21 at 22:23
7

Like stej mentioned, there is the Get-Member cmdlet with a -MemberType parameter you can use:

$documents | Get-Member -MemberType Property | ForEach-Object {
    $_.Name
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martin Brandl
  • 56,134
  • 13
  • 133
  • 172
  • How do you get the value with get-member? – Brain2000 Jan 23 '19 at 17:47
  • @Brain2000 You could parse it from `$_.Definition` or use `$_.Name` to reference the property of your object: `$documents.($_.Name)`. But I would rather go for the solutions of [briantist](https://stackoverflow.com/a/37688746/11942268) and [Kolob Canyon](https://stackoverflow.com/a/46590477/11942268). – stackprotector Apr 28 '20 at 08:32
3

I use the following command to enumerate the properties and put them in a table:

$Object.PSObject.Properties | Format-Table @{ Label = 'Type'; Expression = { "[$($($_.TypeNameOfValue).Split('.')[-1])]" } }, Name, Value -AutoSize -Wrap

I wrote the following function around this, that iterates through nested properties as well.

function Enumerate-ObjectProperties {
    param (
        [psobject] $Object,
        [string] $Root
    )

    Write-Output $($Object.PSObject.Properties | Format-Table @{ Label = 'Type'; Expression = { "[$($($_.TypeNameOfValue).Split('.')[-1])]" } }, Name, Value -AutoSize -Wrap | Out-String)

    foreach ($Property in $Object.PSObject.Properties) {
        # Strings always have a single property "Length". Do not enumerate this.
        if (($Property.TypeNameOfValue -ne 'System.String') -and ($($Object.$($Property.Name).PSObject.Properties))) {
            $NewRoot = $($($Root + '.' + $($Property.Name)).Trim('.'))
            Write-Output "Property: $($NewRoot)"
            Enumerate-ObjectProperties -Object $($Object.$($Property.Name)) -Root $NewRoot
        }
    }
}

Enumerate-ObjectProperties $YourObject

However please do keep in mind that some object are constructed somewhat typical, in the sense that they have 'loops' in their nesting. Subproperties that refer to parent objects. An example being:

Get-Date 'somebogusdata'
Enumerate-ObjectProperties $Error[0]

This will create a loop. You could of course add a 'depth' parameter to add a depth counter, specifying you only want to go to a certain depth. That would look something like this:

$script:Level = 1
function Enumerate-ObjectProperties {
    param (
        [psobject] $Object,
        [int32] $Depth = 1,
        [string] $Root
    )

    Write-Output $($Object.PSObject.Properties | Format-Table @{ Label = 'Type'; Expression = { "[$($($_.TypeNameOfValue).Split('.')[-1])]" } }, Name, Value -AutoSize -Wrap | Out-String)

    foreach ($Property in $Object.PSObject.Properties) {
        # Strings always have a single property "Length". Do not enumerate this.
        if (($Property.TypeNameOfValue -ne 'System.String') -and ($($Object.$($Property.Name).PSObject.Properties)) -and ($Level -le $Depth)) {
            $NewRoot = $($($Root + '.' + $($Property.Name)).Trim('.'))
            $Level++
            Write-Output "Property: $($NewRoot) (Level: $Level)"
            Enumerate-ObjectProperties -Object $($Object.$($Property.Name)) -Root $NewRoot
            $Level--
        }
    }
}

Enumerate-ObjectProperties -Object $Error[0] -Depth 2