Strongly type PS custom object properties - powershell

I have been using hash tables to return complex data from functions, and it has worked well, but I would like to have the keys strongly typed, since I have booleans, strings, arrays of strings, ordered dictionaries and such in the returned hash tables. So, given something like this
[hashtable]$hashtable = #{
one = 1
two = "two"
}
I have the issue that the type of each key is weakly typed. I want to basically do this
[hashtable]$hashtable = #{
[int]one = 1
[string]two = "two"
}
But that's not valid code. So I thought I could do this
[psCustomObject]$object = [psCustomObject]#{
[int]one = 1
[string]two = "two"
}
But that's invalid too. I find this a bit ugly, and it also doesn't work
$object = New-Object -typeName:PSObject
$object | Add-Member -memberType:int -name:'one' -value:1
$object | Add-Member -memberType:string -name:'two' -value:'two'
So, am I SOL and there is no way, or no elegant way, to create a custom object with strongly typed properties?

Inside the hashtable literal you'll want to type-cast the value expression instead:
PS C:\> $object = [PSCustomObject]#{
one = [int]1
two = [string]"two"
}
PS C:\> $object|gm -MemberType NoteProperty
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
one NoteProperty int one=1
two NoteProperty string two=two
This will, however, not prevent anyone from storing a non-integer or non-string in any of the properties - psobject property are simply not strongly typed.
If you want type safety for properties you'll need to create a new type with the class keyword:
class MyOneTwo
{
[int]$One
[string]$Two
MyOneTwo(){}
MyOneTwo([int]$one, [string]$two){
$this.One = $one
$this.Two = $two
}
}
# Create instances with ::new(), New-Object or a cast:
$object = [MyOneTwo]::new(1,"2")
$object = New-Object MyOneTwo -Property #{ One = 1; Two = "2" }
$object = [MyOneTwo]#{ One = 1; Two = "2" }

Related

How do I convert a powershell hashtable to an object?

Some hashtables in PowerShell, such as those imported with Import-PowerShellDataFile, would be much easier to navigate if being a PSCustomObject instead.
#{
AllNodes = #(
#{
NodeName = 'SRV1'
Role = 'Application'
RunCentralAdmin = $true
},
#{
NodeName = 'SRV2'
Role = 'DistributedCache'
RunCentralAdmin = $true
},
#{
NodeName = 'SRV3'
Role = 'WebFrontEnd'
PSDscAllowDomainUser = $true
PSDscAllowPlainTextPassword = $true
CertificateFolder = '\\mediasrv\Media'
},
#{
NodeName = 'SRV4'
Role = 'Search'
},
#{
NodeName = '*'
DatabaseServer = 'sql1'
FarmConfigDatabaseName = '__FarmConfig'
FarmContentDatabaseName = '__FarmContent'
CentralAdministrationPort = 1234
RunCentralAdmin = $false
}
);
NonNodeData = #{
Comment = 'No comment'
}
}
When imported it will become a hashtable of hashtables
$psdnode = Import-PowerShellDataFile .\nodefile.psd1
$psdnode
Name Value
---- -----
AllNodes {System.Collections.Hashtable, System.Collect...
NonNodeData {Comment}
$psdnode.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
and the data structure will be just weird when navigating by property name.
There's good information in the existing answers, but given your question's generic title, let me try a systematic overview:
You do not need to convert a hashtable to a [pscustomobject] instance in order to use dot notation to drill down into its entries (properties), as discussed in the comments and demonstrated in iRon's answer.
A simple example:
#{ top = #{ nested = 'foo' } }.top.nested # -> 'foo'
See this answer for more information.
In fact, when possible, use of hashtables is preferable to [pscustomobject]s, because:
they are lighter-weight than [pscustomobject] instances (use less memory)
it is easier to construct them iteratively and modify them.
Note:
The above doesn't just apply to the [hashtable] type, but more generally to instances of types that implement the [System.Collections.IDictionary] interface or its generic counterpart, System.Collections.Generic.IDictionary[TKey, TValue]], notably including ordered hashtables (which are instances of type System.String, which PowerShell allows you to construct with syntactic sugar [ordered] #{ ... }).
Unless noted, hashtable in the following section refers to all such types.
In cases where you do need to convert a [hasthable] to a [pscustomobject]:
While many standard cmdlets accept [hasthable]s interchangeably with [pscustomobjects]s, some do not, notably ConvertTo-Csv and Export-Csv (see GitHub issue #10999 for a feature request to change that); in such cases, conversion to [pscustomobject] is a must.
Caveat: Hasthables can have keys of any type, whereas conversion to [pscustomobject] invariably requires using string "keys", i.e. property names. Thus, not all hashtables can be faithfully or meaningfully converted to [pscustomobject]s.
Converting non-nested hashtables to [pscustomobject]:
The syntactic sugar PowerShell offers for [pscustomobject] literals (e.g., [pscustomobject] #{ foo = 'bar'; baz = 42 }) also works via preexisting hash; e.g.:
$hash = #{ foo = 'bar'; baz = 42 }
$custObj = [pscustomobject] $hash # Simply cast to [pscustomobject]
Converting nested hashtables, i.e. an object graph, to a [pscustomobject] graph:
A simple, though limited and potentially expensive solution is the one shown in your own answer: Convert the hashtable to JSON with ConvertTo-Json, then reconvert the resulting JSON into a [pscustomobject] graph with ConvertFrom-Json.
Performance aside, the fundamental limitation of this approach is that type fidelity may be lost, given that JSON supports only a few data types. While not a concern with a hashtable read via Import-PowerShellDataFile, a given hashtable may contain instances of types that have no meaningful representation in JSON.
You can overcome this limitation with a custom conversion function, ConvertFrom-HashTable (source code below); e.g. (inspect the result with Format-Custom -InputObject $custObj):
$hash = #{ foo = 'bar'; baz = #{ quux = 42 } } # nested hashtable
$custObj = $hash | ConvertFrom-HashTable # convert to [pscustomobject] graph
ConvertFrom-HashTable source code:
Note: Despite the name, the function generally supports instance of types that implement IDictionary as input.
function ConvertFrom-HashTable {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[System.Collections.IDictionary] $HashTable
)
process {
$oht = [ordered] #{} # Aux. ordered hashtable for collecting property values.
foreach ($entry in $HashTable.GetEnumerator()) {
if ($entry.Value -is [System.Collections.IDictionary]) { # Nested dictionary? Recurse.
$oht[$entry.Key] = ConvertFrom-HashTable -HashTable $entry.Value
} else { # Copy value as-is.
$oht[$entry.Key] = $entry.Value
}
}
[pscustomobject] $oht # Convert to [pscustomobject] and output.
}
}
What is the issue/question?
#'
#{
AllNodes = #(
#{
NodeName = 'SRV1'
Role = 'Application'
RunCentralAdmin = $true
},
#{
NodeName = 'SRV2'
Role = 'DistributedCache'
RunCentralAdmin = $true
},
#{
NodeName = 'SRV3'
Role = 'WebFrontEnd'
PSDscAllowDomainUser = $true
PSDscAllowPlainTextPassword = $true
CertificateFolder = '\\mediasrv\Media'
},
#{
NodeName = 'SRV4'
Role = 'Search'
},
#{
NodeName = '*'
DatabaseServer = 'sql1'
FarmConfigDatabaseName = '__FarmConfig'
FarmContentDatabaseName = '__FarmContent'
CentralAdministrationPort = 1234
RunCentralAdmin = $false
}
);
NonNodeData = #{
Comment = 'No comment'
}
}
'# |Set-Content .\nodes.psd1
$psdnode = Import-PowerShellDataFile .\nodefile.psd1
$psdnode
Name Value
---- -----
NonNodeData {Comment}
AllNodes {SRV1, SRV2, SRV3, SRV4…}
$psdnode.AllNodes.where{ $_.NodeName -eq 'SRV3' }.Role
WebFrontEnd
A very simple way, that I discovered just yesterday, is to do a "double-convert" over JSON.
$nodes = Import-PowerShellDataFile .\nodes.psd1 | ConvertTo-Json | ConvertFrom-Json
$nodes
AllNodes
--------
{#{NodeName=SRV1; RunCentralAdmin=True; Role=Application}, #{NodeName=SRV2; RunCentralAdm...}
$nodes.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object

Creation and modification of Objects in Powershell

I'm a newbie of powershell, I'm starting right now to look at objects, etc.
I'm creating an object in this way:
$myObject = [PSCustomObject]#{
ComputerName = "abc"
Data = "xxx"
}
If I then print $myObject what i get is:
ComputerName Data
------------ ----
abc xxx
And everything is ok, now I want to add a property to that object, and I saw (tell me if i'm wrong) that I can do it in 2 ways: with add-member and with select-object
For example with add-member what I did was:
$myObject | Add-member -NotePropertyName Level -NotePropertyValue Highest
Instead with Select-object I did:
$myobject = 2 (cause i want to add 2 properties, is it right?) | Select-Object -Property Level, Privilege
$myobject.Level = "High"
$myobject.Privilege = "Elevated"
Now if I run $myobject I still only get:
ComputerName Data
------------ ----
abc xxx
What should I do to see all the data, even the one that I added later?
Can I directly add the values to the properties added through Select-Object?
Thanks
You can use the Add-Member method on a PsCustomObject.
$myObject = [PSCustomObject]#{
ComputerName = "abc"
Data = "xxx"
}
$myObject | Add-Member -NotePropertyName Level -NotePropertyValue High
$myObject | Add-Member Privilege Elevated
$myObject
#Output looks like,
ComputerName Data Level Privilege
------------ ---- ----- ---------
abc xxx High Elevated
Update
Not sure at the moment why but will elaborate on it ...
If you print the Pscustomobject and then run the add-member, they seem to be ignored. If you create a hashtable, update it and then convert to PsObject, it works. Following is an example of that hashtable
$myObject = #{
ComputerName = "abc"
Data = "xxx"
}
$myObject | ft
$myObject.Add("Level", "high")
$myObject.Add("Privilege", "Elevated")
[pscustomobject] $myObject | ft
What I found
When you print $myObject then add the data, the data is added but not displayed. This is due to some internal mechanism, unknown to me, that continues to use the same headers from previous command.
If you notice, the data is printed twice under the same heading. If you want to see the differences before and after, pipe it to format-list or format-table to use a different output stream each time.
$myObject = [PSCustomObject]#{
ComputerName = "abc"
Data = "xxx"
}
$myObject | ft
$myObject | Add-Member -NotePropertyName Level -NotePropertyValue High -Force
$myObject | Add-Member Privilege Elevated
$myObject | ft
You can use either Add-Member or Select-Object. Withstanding the advantages or disadvantages in different situations I just want to throw in the Select-Object example. Just so you have both methods:
This example will echo the object with the selected properties:
$myObject |
Select-Object *,
#{Name = 'Level'; Expression = { 'High' } },
#{Name = 'Privilege'; Expression = { 'Elevated' } }
If you want to save the new properties back to the object variable you'll have to reassign like below:
$myObject = $myObject |
Select-Object *,
#{Name = 'Level'; Expression = { 'High' } },
#{Name = 'Privilege'; Expression = { 'Elevated' } }
PowerShell allows you to provide a hash table to define the new properties. You'll note it looks fairly similar to the hash you used to create the object. Typically the expression would leverage the $_ syntax to calculate the property's value. You will often here these referred to as calculated properties.

Is it possible to use the value of a variable, as a new variable name?

Long time listener, first time caller! :) So I FEEL like this is possible, it's just that the solution eludes me.
I'm setting a variable ($thisName = "a") and using that to create an object.
I'd LIKE to use $thisName as, not only a property, but also the name of the object.
IS this possible?
$thisName = "a"
$theseParams = #{ Name = $thisName; DebugMode = $true }
$thisName = New-Object [PSCustomObject] -Property #theseParams
I'm WANTING the variable name of the object to be $a, as well as the $a.Name to be "a". But I'm just resetting (or outright breaking) $thisName.
I believe New-Variable may help:
$thisName = "a"
$theseParams = #{ Name = $thisName; DebugMode = $true }
New-Variable -Name $thisName -Value $theseParams
The output seems to be what you're asking for:
PS C:\> $a.Name
a
PS C:\> $a
Name Value
---- -----
DebugMode True
Name a
Hope this helps.

Powershell: Storing data

I would appreciate if someone could help me with fundamental ways to store data in a tabulated manner in PowerShell, specifically how to add values to a previously created table.
I'd also appreciate if someone could show a code example of the best way to do this kind of thing.
Apologies in advance if this is too fundamental for this arena. I would appreciate being pointed in a useful direction.
Basically, I want to store a collection of plaintext user names and passwords. Nothing fancy so I don't need encryption (I know about the PSCredential object. It's not required)
Method 1 - Custom Object
$account = New-Object -TypeName PSObject
Add-Member -InputObject $PRCGuest -MemberType NoteProperty `
-Name UserName -Value ""
Add-Member -InputObject $PRCGuest -MemberType NoteProperty `
-Name Password -Value ""
I know how to assign initial values to this objects members...
$account.UserName = "jimbo"
$account.Password = "1234
but what is the correct syntax to add further values and is creating an object like this to store lists of data correct?
Method 2 - Hash table
$account = #{
"jimbo" = "1234";
"jimmy" = "2346"
}
I've read that you can create a hashtable and then copy the value pairs into a custom object. How is this done and why do you need this intermediate step?
Finally, are there any good books, ideally language agnostic (as I will be learning C++ soon) that show how to efficiently store tables of data in high level object oriented languages.
Thanks for any help.
I prefer the hashtable method as I think it is cleaner and easier to read. There are a few methods to achieve this depending on which version of PowerShell you are using.
Example 1
$props = #{
Property1 = 'one'
Property2 = 'two'
Property3 = 'three'
}
$object = new-object psobject -Property $props
$object | Select-Object Property1, Property2
Example 2 REQUIRES V3
$obj = [PSCustomObject]#{
Property1 = 'one'
Property2 = 'two'
Property3 = 'three'
}
$OBJ | Select-Object Property1
Here is the Link for Reference
http://social.technet.microsoft.com/wiki/contents/articles/7804.powershell-creating-custom-objects.aspx

Difference between PSObject, Hashtable, and PSCustomObject

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