Reputation: 1784
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.
Upvotes: 6
Views: 13505
Reputation: 437558
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:
[pscustomobject]
instances (use less memory)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.Collections.Specialized.OrderedDictionary
, 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
.
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[[object] $entry.Key] = ConvertFrom-HashTable -HashTable $entry.Value # NOTE: Casting to [object] prevents problems with *numeric* hashtable keys.
} else { # Copy value as-is.
$oht[[object] $entry.Key] = $entry.Value
}
}
[pscustomobject] $oht # Convert to [pscustomobject] and output.
}
}
Upvotes: 15
Reputation: 23663
@'
@{
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
Upvotes: 3
Reputation: 1784
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
Upvotes: 2