How to better build [PSCustomObject]

Hi.

I’m building a .csv from a list of Virtual Machines in our VMWare environment. The following code that build the table works, but it gets tedious having to add each an every line.

My question is this; Is there a better way to create a couple of data fields (columns) using a couple of selected items, and then just add the rest that are returned from the Select-Object * without having to specify each one?

As you can see in the following code, I am only selecting and pre-formatting $VMName and $VMOs, but I would like the rest of the returned fields just as they are returned from the select with their default names.

Get-VM | Select-Object * |
    ForEach-Object {
        $VMName = $_.Name
        $VMOs   = $_.Guest.OSFullName
        $OutTbl += [PSCustomObject] @{
            Name               = $VMName
            PowerState         = $_.PowerState
            Notes              = $_.Notes
            OS                 = $VMOs
            NumCpu             = $_.NumCpu
            CoresPerSocket     = $_.CoresPerSocket
            MemoryMB           = $_.MemoryMB
            MemoryGB           = $_.MemoryGB
            VMHostId           = $_.VMHostId
            VMHost             = $_.VMHost
            Folder             = $_.Folder
            ResourcePoolId     = $_.ResourcePoolId
            ResourcePool       = $_.ResourcePool
            UsedSpaceGB        = $_.UsedSpaceGB
            ProvisionedSpaceGB = $_.ProvisionedSpaceGB
        }
    }

Any suggestions greatly appreciated.

Thank you.

Change your Select-Object statement to only include the properties you want. If you need to change a name (like you do for OS) add it as a calculated property. Then you can can just directly export the Results of (abbreviated)

Get-VM | Select-Object Prop1,Prop2,... | Export-Csv -Path C:\...\file.csv

Hi.

Thank you for the quick response. I need to report on all fields, hence the Select * statement. I need to add two custom fields to my table, followed by the rest of the fields returned.

You can simply add properties to existing objects.

# If $VMs is the output of Get-VM | Select-Object *
$i = 0; $VMs | % { $_ | Add-Member -NotePropertyName "Test" -NotePropertyValue $i; $i++}

This would add a property called test to each and it simply increments by 1 for each object in $VMs

You can still use * for all properties. The key to his suggestion is to use a calculated property to adjust away from the default properties/values.

Before I show you that suggestion I want to point out that this is unnecessary.

$VMName = $_.Name

Name               = $VMName

Name would’ve already been name, so no need for that. So now it appears the only different property is the OS. I will assume that OS is a property already, but if it’s not… just remove the excludeproperty.

$OutTbl = Get-VM | Select-Object -ExcludeProperty OS -Property *,@{n='OS';e={$_.Guest.OSFullName}} 

Of course that will tack the property onto the end. You could put it in the beginning too like

$OutTbl = Get-VM | Select-Object -ExcludeProperty OS -Property @{n='OS';e={$_.Guest.OSFullName}}, *

If you want to dictate the order of the properties, you’ll have to build it like you were with the PSCustomObject.

Finally, please get out of the habit of using

$outTbl += 

to collect output. In small numbers it’s not that bad but very quickly this can cost huge amounts of memory. In your original example, simply capture the output of all your object by putting the variable assignment in front of the loop

$OutTbl = Get-VM | Select-Object * |
    ForEach-Object {
        $VMName = $_.Name
        $VMOs   = $_.Guest.OSFullName
        [PSCustomObject] @{
            Name               = $VMName
            PowerState         = $_.PowerState
            Notes              = $_.Notes
            OS                 = $VMOs
            NumCpu             = $_.NumCpu
            CoresPerSocket     = $_.CoresPerSocket
            MemoryMB           = $_.MemoryMB
            MemoryGB           = $_.MemoryGB
            VMHostId           = $_.VMHostId
            VMHost             = $_.VMHost
            Folder             = $_.Folder
            ResourcePoolId     = $_.ResourcePoolId
            ResourcePool       = $_.ResourcePool
            UsedSpaceGB        = $_.UsedSpaceGB
            ProvisionedSpaceGB = $_.ProvisionedSpaceGB
        }
    }

That will work. Is it possible to add a second field as an exclude property in order to control the order?

For example, Name first, followed by OS, and finally * ?

Thank you.

I just tested this and I believe it is possible.

For testing:

$VMs = Get-VM
$VMs[0] | Select-Object -Property *

That will return all of the properties of a single VM object so you can see them.

Name                    : ComputerMusheen
PowerState              : PoweredOn
Notes                   : For testing purposes
Guest                   : ComputerMusheen:Microsoft Windows 10 (64-bit)
NumCpu                  : 4
CoresPerSocket          : 1
MemoryMB                : 16384
MemoryGB                : 16
VMHostId                : HostSystem-host-123456
VMHost                  : esxi01.contoso.local
VApp                    :
FolderId                : Folder-group-v654321
Folder                  : Workstations
ResourcePoolId          : ResourcePool-resgroup-321654
ResourcePool            : Resources
HARestartPriority       : ClusterRestartPriority
HAIsolationResponse     : AsSpecifiedByCluster
DrsAutomationLevel      : AsSpecifiedByCluster
VMSwapfilePolicy        : Inherit
VMResourceConfiguration : CpuShares:Normal/4000 MemShares:Normal/163840
Version                 : Unknown
HardwareVersion         : vmx-19
PersistentId            : 90eb8581-d5dd-4ade-8fd1-f7e9bd2f84b8
GuestId                 : windows9_64Guest
UsedSpaceGB             : 0.0311394734308123588562011719
ProvisionedSpaceGB      : 0.0311394734308123588562011719
DatastoreIdList         : {Datastore-datastore-098765, Datastore-datastore-162534}
CreateDate              : 4/19/2023 8:02:41 PM
SEVEnabled              : False
BootDelayMillisecond    : 0
MigrationEncryption     : Opportunistic
MemoryHotAddEnabled     : True
MemoryHotAddIncrement   : 4
MemoryHotAddLimit       : 262144
CpuHotAddEnabled        : True
CpuHotRemoveEnabled     : False
ExtensionData           : VMware.Vim.VirtualMachine
CustomFields            : {[XdConfig, ]}
Id                      : VirtualMachine-vm-019284
Uid                     : /VIServer=contoso\admin@vcenter.contoso.local:443/VirtualMachine=VirtualMachine-vm-019284/

If you want the Name, the OS from within the Guest property, and then everything else you can combine the -Property parameter and the -ExcludeProperty parameter like so.

$VMs[0] | Select-Object -Property Name,@{N="OS";E={$_.guest.osfullname}},* -ExcludeProperty Guest

For the following output

Name                    : ComputerMusheen
OS                      : Microsoft Windows 10 (64-bit)
PowerState              : PoweredOn
Notes                   : For testing purposes
NumCpu                  : 4
CoresPerSocket          : 1
MemoryMB                : 16384
MemoryGB                : 16
VMHostId                : HostSystem-host-123456
VMHost                  : esxi01.contoso.local
VApp                    :
FolderId                : Folder-group-v654321
Folder                  : Workstations
ResourcePoolId          : ResourcePool-resgroup-321654
ResourcePool            : Resources
HARestartPriority       : ClusterRestartPriority
HAIsolationResponse     : AsSpecifiedByCluster
DrsAutomationLevel      : AsSpecifiedByCluster
VMSwapfilePolicy        : Inherit
VMResourceConfiguration : CpuShares:Normal/4000 MemShares:Normal/163840
Version                 : Unknown
HardwareVersion         : vmx-19
PersistentId            : 90eb8581-d5dd-4ade-8fd1-f7e9bd2f84b8
GuestId                 : windows9_64Guest
UsedSpaceGB             : 0.0311394734308123588562011719
ProvisionedSpaceGB      : 0.0311394734308123588562011719
DatastoreIdList         : {Datastore-datastore-098765, Datastore-datastore-162534}
CreateDate              : 4/19/2023 8:02:41 PM
SEVEnabled              : False
BootDelayMillisecond    : 0
MigrationEncryption     : Opportunistic
MemoryHotAddEnabled     : True
MemoryHotAddIncrement   : 4
MemoryHotAddLimit       : 262144
CpuHotAddEnabled        : True
CpuHotRemoveEnabled     : False
ExtensionData           : VMware.Vim.VirtualMachine
CustomFields            : {[XdConfig, ]}
Id                      : VirtualMachine-vm-019284
Uid                     : /VIServer=contoso\admin@vcenter.contoso.local:443/VirtualMachine=VirtualMachine-vm-019284/

Then, if it were me, and you want to get fancy you could store your Select-Object parameters/values in a hashtable and splat them

$SelectArgs = @{
    Property = @(
        "Name",
        @{Name="OS";Expression={$_.Guest.OSFullName}},
        "*"
    )
    ExcludeProperty = @(
        "Guest",
        "VMHostId",
        "VApp",
        "FolderId",
        "ResourcePoolId",
        "HARestartPriority",
        "HAIsolationResponse",
        "DrsAutomationLevel",
        "VMSwapfilePolicy",
        "VMResourceConfiguration"
    )
}

$Results = $VMs | Select-Object @SelectArgs

This will throw an error for every single object because we’re asking it to include the Name property, and then also capturing that with the wildcard statement so if you want you can include the ErrorAction parameter in the splat

$SelectArgs = @{
    Property = @(
        "Name",
        @{Name="OS";Expression={$_.Guest.OSFullName}},
        "*"
    )
    ExcludeProperty = @(
        "Guest",
        "VMHostId",
        "VApp",
        "FolderId",
        "ResourcePoolId",
        "HARestartPriority",
        "HAIsolationResponse",
        "DrsAutomationLevel",
        "VMSwapfilePolicy",
        "VMResourceConfiguration"
    )
    ErrorAction = "SilentlyContinue"
}

Or you could use the calculated property method to turn “Name” in to something that doesn’t have a colision like “VMName” or something, then add “Name” to the exclusion list.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.