58

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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
omni
  • 4,104
  • 8
  • 48
  • 67

7 Answers7

48

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tkokasih
  • 1,117
  • 9
  • 19
38

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
Zombo
  • 1
  • 62
  • 391
  • 407
  • 4
    Up voting because this was exactly the answer I was looking for. – Doug Coburn Jan 06 '15 at 15:53
  • Same here. A `hashtable` gave me "Name, Value", which made it very difficult to work with. Using `PSCustomObject` allowed me to pipe normally. – Tyler Montney Jan 21 '20 at 18:57
  • @Doug & @Tyler - I know these comments are a little old, but "pipe normally" isn't necessarily what I would call the given example. If you already have an object that you can pipe to `New-Item`; great. If not, the `hashtable` would be "more normal" in my opinion, but without piping. See [`about_Splatting`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting) for more info, basically `$hashtable=@{Path='foo'; Type='directory'}` then `New-Item @hashtable` – immobile2 Nov 17 '21 at 04:41
35

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.
mklement0
  • 382,024
  • 64
  • 607
  • 775
Dave F
  • 1,837
  • 15
  • 20
  • 4
    Upvoting this because this appears to be more detailed about the differences between each with supporting reference material. – Jim May 15 '18 at 15:07
  • 3
    Good stuff. Note that `[PSCustomObject]@{a=1;b=2;c=3}` is _syntactic sugar_ that _directly_ constructs a custom object, not via an intermediate hashtable (despite what the syntax suggests); if an intermediate hashtable were involved, the ordering of properties wouldn't be guaranteed, but it is. Note that type accelerators `[pscustomobject]` and `[psobject]` refer to the _same_ type, `System.Management.Automation.PSObject`. However, a "pure" `PSObject` (a custom object that has _only_ ETS properties (doesn't wrap a .NET object) reports its type as `System.Management.Automation.PSCustomObject`. – mklement0 Feb 17 '20 at 19:29
14

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)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Loïc MICHEL
  • 24,935
  • 9
  • 74
  • 103
7

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

Daniel Liuzzi
  • 16,807
  • 8
  • 52
  • 57
Adam Driscoll
  • 9,395
  • 9
  • 61
  • 104
  • 4
    Well, performance - okay. But is that everything? Also the performance is pro hashtable. So what's the right to exist for the PSCustomObject? And the default display option, really? Common we're dealing with data - nobody wants to display that stuff every day. And now and then, when I need to display some data, I still may build a small loop and use standard formatters. Doesn't seem sufficient to justify a new datatype to me. – omni Dec 23 '12 at 23:19
  • 3
    The article in the first link says nothing about the speed of psobject vs pscustomobject. Instead it highlights the difference between an O(m*n) and an O(m*ln n) algorithm. – JJJ Sep 04 '13 at 07:35
  • 6
    "Always quote the most relevant part of an important link, in case the target site is unreachable or goes permanently offline." - http://stackoverflow.com/help/how-to-answer – Pete Mar 06 '15 at 01:14
  • It is interesting that I just ran script, only 10 times on my local, there time difference was nowhere near as big. I am assuming there were some optimizations between this users version of Powershell and mine (version 5.1) or maybe other factors including the load of the machine. Anyway, my results were consistently around 7-8 seconds for Method 1 and just over 1.5 second for Method 2. – Explicitsoul Aug 30 '18 at 11:05
  • 2
    the above code does not prove anything except that piping is slow. use this code as method1 and its faster than the other: $method1 = { foreach ($object in $objects) { $value = foreach($lookup in $lookupobjects) { if ($lookup.Path -eq $object.Path) { $lookup.share; break } } $object | Add-Member NoteProperty -Name Share -Value $value } } – Carsten Jun 12 '20 at 14:46
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
}
0

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

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

Hemendr
  • 673
  • 6
  • 12