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:
[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.
}
}