Reputation: 1659
What is the easiest way to convert a PSCustomObject
to a Hashtable
? It displays just like one with the splat operator, curly braces and what appear to be key value pairs. When I try to cast it to [Hashtable]
it doesn't work. I also tried .toString()
and the assigned variable says its a string but displays nothing - any ideas?
Upvotes: 98
Views: 122546
Reputation: 23663
Probably a little over the top for this question, but I created a ObjectGraphTools module that includes a Copy-ObjectGraph cmdlet that is able to (recursively) convert any array like node to any other array like node (-ListAs <type or example>
) and any map like node (incloding dictionaries and PSCustomObjext
s) to any other map like node (-MapAs <type or example>
).
Example command:
$Object | Copy-ObjectGraph -ListAs Collections.Generic.List[object] -MapAs @{}
Upvotes: 0
Reputation: 1568
Here's a version that works with nested hashtables / arrays as well, which is useful if you're trying to do this with DSC ConfigurationData:
function ConvertPSObjectToHashtable
{
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
{
$collection = @(
foreach ($object in $InputObject) { ConvertPSObjectToHashtable $object }
)
Write-Output -NoEnumerate $collection
}
elseif ($InputObject -is [psobject])
{
$hash = @{}
foreach ($property in $InputObject.PSObject.Properties)
{
$hash[$property.Name] = (ConvertPSObjectToHashtable $property.Value).PSObject.BaseObject
}
$hash
}
else
{
$InputObject
}
}
}
Upvotes: 36
Reputation: 852
Hash table deep clone except we swap out PSObjects for hashtables when we find them. Otherwise it behaves the same as the function it's based on.
<#
.SYNOPSIS
Converts a PSObject to a hashtable by doing a deep clone
and converting PSObjects to Hashtables on the fly.
.NOTES
This function is based on Kevin Marquette's Get-DeepClone
function as documented below.
https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-hashtable?view=powershell-7.3#deep-copies
.EXAMPLE
$Settings = [PSObject]@{
foo = "foo"
one = @{ two = "three" }
four = [PSObject]@{ five = "six" }
seven = @( @("eight", "nine") )
}
$Clone = Convert-PSObjectToHashtable $Settings
#>
function Convert-PSObjectToHashtable {
[CmdletBinding()]
param(
[Parameter(Mandatory,ValueFromPipeline)]
[Object] $InputObject
)
process {
$Clone = @{}
switch ($InputObject.GetType().Name) {
'PSCustomObject' {
foreach ($Property in $InputObject.PSObject.Properties) {
$Clone[$Property.Name] = Convert-PSObjectToHashtable $Property.Value
}
return $Clone
}
'Hashtable' {
foreach ($Key in $InputObject.Keys) {
$Clone[$Key] = Convert-PSObjectToHashtable $InputObject[$Key]
}
return $Clone
}
default { return $InputObject }
}
}
}
Upvotes: 0
Reputation: 375
Based on Dave's suggestion, here's a truly universal conversion function. It mimics CovertTo-Json | ConvertFrom-Json -AsHashTable
deliberately only partly because this has some limitations:
Depth 6
: it takes a long time and at the end I get an OutOfMemoryException
(PowerShell 7.3, 16 GB installed memory).XmlDocument
, Int128
or BigInt
object. Of course, the latter two are not defined according to the JSON 'standard'. But it would at least be possible to convert them to type String
or throw an exception.Hidden properties of objects are not taken into account, since this behavior of ConvertTo-Json
is subject to some criticism anyway. Further explanations can be found in the comment-based help of the function.
function ConvertTo-Hashtable {
<#
.DESCRIPTION
This function converts arbitrary objects into HashTables of their properties. If one of the properties is an object, the procedure is the same, up to the
specified nesting depth. If this is reached, the properties of the object in question are no longer read out, but its ToString() method is called.
All properties of root objects whose name does not begin with 'PS' are taken into account.
The following properties are taken into account for all other objects:
- Properties of the PSMemberType 'Property' unrestricted
- Properties of the PSMemberType 'NoteProperty' only if the property value's type is PSCustomObject
- all other PSMemberTypes only if they have the PSMemberType of the preceding property in the hierarchy (affects ScriptProperty)
Restrictively, properties marked as 'hidden' or referencing the parent object are ignored.
Excluded from conversion to HashTables are all objects of types for which PowerShell allows remote delivery of 'live' objects. Objects of these types are kept
unchanged. This affects the primitive and almost primitive types, as explained at
https://devblogs.microsoft.com/powershell/how-objects-are-sent-to-and-from-remote-sessions/. Likewise, the composition of objects in arrays (collections that
implement IEnumerable) or associative arrays (collections that implement IDictionary) is preserved. This circumvents the JSON 'standard' restrictions on
convertible types imposed when using ConvertFrom-Json with the -AsHashTable switch.
A conversion of a complex object into a HashTable can be advantageous if this is to be transmitted remotely. The nesting depth of these objects is now not
subject to any restrictions. If, on the other hand, a complex object is transmitted remotely directly, PowerShell only transfers arrays and HashTables without
restrictions regarding their nesting depth. However, objects of a different type contained therein are only transmitted in the form of the result of their
ToString() method reaching a nesting depth of 2. All of their properties are lost. So an Object(Arrays(Object with different properties)) becomes a
PSObject(Arrays(String)).
If this function is used for remote transmission, it is only necessary to ensure that a mechanism is established on the receiving side to convert the HashTable
structure back into a 'living' object of the relevant classes. The simplest possibility is to equip these with an op_Implicit operator that takes a
HashTable. A suitable constructor of the class must then be called within the operator. This means that automated casting, e.g. of typed arrays, is also possible.
These links may be helpful: https://stackoverflow.com/questions/58330233/how-to-convert-pscustomobject-with-additional-props-to-a-custom-class/ and
https://stackoverflow.com/questions/59899360/how-do-i-pass-a-class-object-in-a-argument-list-to-a-another-computer-and-call-a/76695304#76695304.
.PARAMETER InputObject
The object to be convertet into a HashTable. There are no type restrictions.
.PARAMETER Depth
The level of nesting to which the properties of objects are considered. Beyond that, its ToString() method is called.
.PARAMETER NoEnumerate
Specifies that output isn't enumerated. The automatic enumeration and the function of this switch is documented in the following table. It also documents
under which circumstances the nesting depth is reduced.
passed value return if passed via pipeline return if passed as parameter
parameter isn't set (*automatic enumeration):
$null $null $null
item item, depth reduced by 1 item
[] $null * $null
[$null] * $null * $null
[item] * item, depth reduced by 1 * item, depth reduced by 1
[items] [items] [items]
parameter is set (*changes due to the set parameter):
$null * [$null] $null
item * [item] item
[] * [] * []
[$null] * [$null] * [$null]
[item] * [item] * [item]
[items] [items] [items]
.PARAMETER EnumsAsStrings
If this switch is set, Enums will be converted to their string representation. Otherwise their numeric value will be taken.
.LINK
https://stackoverflow.com/questions/3740128/pscustomobject-to-hashtable
#>
[CmdletBinding()]
[SuppressMessage('PSUseOutputTypeCorrectly', '', Justification = 'Returns many types')]
param (
[Parameter(ValueFromPipeline)] [Object]$InputObject,
[Parameter()] [ValidateRange(1, 100)] [Int32]$Depth = 2,
[Parameter()] [Switch]$NoEnumerate,
[Parameter()] [Switch]$EnumsAsStrings
)
begin {
function InnerConvert(
[Object]$InputObject,
[Int32]$Depth,
[Int32]$DepthCtr,
[Boolean]$EnumsAsStrings,
[Int32]$ObjectDepthStatus,
[PSMemberTypes]$ParentMemberType
) {
if ($null -eq $InputObject) {
$null
} elseif ($InputObject -is [Char] -or $InputObject -is [String] -or
$InputObject -is [Byte] -or $InputObject -is [SByte] -or $InputObject -is [Int16] -or $InputObject -is [UInt16] -or
$InputObject -is [Int32] -or $InputObject -is [UInt32] -or $InputObject -is [Int64] -or
$InputObject -is [Single] -or $InputObject -is [Double] -or $InputObject -is [DateTime] -or $InputObject -is [Boolean] -or
$InputObject -is [Decimal] -or $InputObject -is [Uri] -or $InputObject -is [Xml.XmlDocument] -or
$InputObject -is [ProgressRecord] -or $InputObject -is [TimeSpan] -or $InputObject -is [Guid] -or
$InputObject -is [Version] -or $InputObject -is [UInt64]
) {
$InputObject
} elseif ($InputObject -is [Enum]) {
if ($EnumsAsStrings) {
$InputObject.ToString()
} else {
[Int32]$InputObject
}
} elseif ($InputObject -is [SecureString] ) {
$InputObject | ConvertFrom-SecureString
} elseif ($InputObject -is [UInt64] -or $InputObject.GetType().Name -in ('Int128', 'UInt128') -or $InputObject -is [BigInt]) {
[String]$InputObject
} else {
if ($DepthCtr -le $Depth) {
if ($InputObject -is [IDictionary]) {
try {
[OrderedHashTable]$resultTable = [ordered]@{}
} catch {
[HashTable]$resultTable = @{}
}
foreach ($item in $InputObject.GetEnumerator()) {
$resultTable[$item.Key] = InnerConvert -InputObject $item.Value -Depth $Depth -DepthCtr ($DepthCtr + 1) `
-EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus $ObjectDepthStatus -ParentMemberType 0
}
$resultTable
} elseif ($InputObject -is [IEnumerable]) {
[Object[]]$resultArray = @(
foreach ($item in $InputObject) {
InnerConvert -InputObject $item -Depth $Depth -DepthCtr ($DepthCtr + 1) `
-EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus $ObjectDepthStatus -ParentMemberType 0
}
)
, $resultArray
} else {
# One must not test for [PSObject] because of some object properties are not of this type, e.g. RuntimeTypeHandle
try {
[OrderedHashTable]$resultTable = [ordered]@{}
} catch {
[HashTable]$resultTable = @{}
}
$ParentMemberType = $ParentMemberType -band -bnot [PSMemberTypes]::NoteProperty
[PSMemberTypes]$alwaysIncludedTypes = [PSMemberTypes]::Property -bor $ParentMemberType
foreach ($property in $InputObject.PSObject.Properties) {
if (-not $InputObject.Equals($property.Value)) {
if ($property.MemberType -band $alwaysIncludedTypes -or (
$ObjectDepthStatus -lt 2 -and $property.Name -inotlike 'PS*'
) -or (
$property.MemberType -band [PSMemberTypes]::NoteProperty -and (
$null -eq $property.Value -or $property.Value.GetType().Name -in ('PSCustomObject', 'PSObject'))
)
) {
$resultTable[$property.Name] = InnerConvert -InputObject $property.Value -Depth $Depth -DepthCtr ($DepthCtr + 1) `
-EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus 2 -ParentMemberType $property.MemberType
}
}
}
if ($resultTable.Count -or $InputObject -isnot [ValueType]) {
$resultTable
} else {
[String]$InputObject
}
}
} else {
[String]$InputObject
}
}
}
if ($MyInvocation.ExpectingInput) {
[Object]$completeInput = [ArrayList]::new()
} else {
[Object]$completeInput = $null
}
} process {
if ($MyInvocation.ExpectingInput) {
[void]$completeInput.Add($_)
} else {
$completeInput = $InputObject
}
} end {
[Int32]$currentDepth = $Depth
if ($MyInvocation.ExpectingInput -or $completeInput -is [Array]) {
# don't enumerate HashTables and other IEnumerable
if ($completeInput.Count -eq 0) {
if (-not $NoEnumerate) {
$completeInput = $null
}
} elseif ($completeInput.Count -eq 1) {
if ($NoEnumerate) {
if ($MyInvocation.ExpectingInput) {
$completeInput = $completeInput[0]
} else {
$currentDepth--
}
} else {
$completeInput = $completeInput[0]
$currentDepth--
}
}
}
InnerConvert -InputObject $completeInput -Depth $currentDepth -DepthCtr 0 `
-EnumsAsStrings $EnumsAsStrings -ObjectDepthStatus 0 -ParentMemberType 0
}
}
Upvotes: 2
Reputation: 41
For simple [PSCustomObject] to [Hashtable] conversion Keith's Answer works best.
However if you need more options you can use
function ConvertTo-Hashtable {
<#
.Synopsis
Converts an object to a hashtable
.DESCRIPTION
PowerShell v4 seems to have trouble casting some objects to Hashtable.
This function is a workaround to convert PS Objects to [Hashtable]
.LINK
https://github.com/alainQtec/.files/blob/main/src/scripts/Converters/ConvertTo-Hashtable.ps1
.NOTES
Base ref: https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/turning-objects-into-hash-tables-2
#>
PARAM(
# The object to convert to a hashtable
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
$InputObject,
# Forces the values to be strings and converts them by running them through Out-String
[switch]$AsString,
# If set, empty properties are Included
[switch]$AllowNulls,
# Make each hashtable to have it's own set of properties, otherwise,
# (default) each InputObject is normalized to the properties on the first object in the pipeline
[switch]$DontNormalize
)
BEGIN {
$headers = @()
}
PROCESS {
if (!$headers -or $DontNormalize) {
$headers = $InputObject | Get-Member -type Properties | Select-Object -expand name
}
$OutputHash = @{}
if ($AsString) {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col | Out-String -Width 9999 | ForEach-Object { $_.Trim() }
}
}
} else {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col
}
}
}
}
END {
return $OutputHash
}
}
Maybe this is overkill but I hope it Helps
Upvotes: 0
Reputation: 643
My extremely lazy approach, enabled by a new feature in PowerShell 6:
$myhashtable = $mypscustomobject | ConvertTo-Json | ConvertFrom-Json -AsHashTable
Upvotes: 27
Reputation: 27
My code:
function PSCustomObjectConvertToHashtable() {
param(
[Parameter(ValueFromPipeline)]
$object
)
if ( $object -eq $null ) { return $null }
if ( $object -is [psobject] ) {
$result = @{}
$items = $object | Get-Member -MemberType NoteProperty
foreach( $item in $items ) {
$key = $item.Name
$value = PSCustomObjectConvertToHashtable -object $object.$key
$result.Add($key, $value)
}
return $result
} elseif ($object -is [array]) {
$result = [object[]]::new($object.Count)
for ($i = 0; $i -lt $object.Count; $i++) {
$result[$i] = (PSCustomObjectConvertToHashtable -object $object[$i])
}
return ,$result
} else {
return $object
}
}
Upvotes: 1
Reputation: 201652
Shouldn't be too hard. Something like this should do the trick:
# Create a PSCustomObject (ironically using a hashtable)
$ht1 = @{ A = 'a'; B = 'b'; DateTime = Get-Date }
$theObject = new-object psobject -Property $ht1
# Convert the PSCustomObject back to a hashtable
$ht2 = @{}
$theObject.psobject.properties | Foreach { $ht2[$_.Name] = $_.Value }
Upvotes: 123
Reputation: 7678
This works for PSCustomObjects created by ConvertFrom_Json.
Function ConvertConvertFrom-JsonPSCustomObjectToHash($obj)
{
$hash = @{}
$obj | Get-Member -MemberType Properties | SELECT -exp "Name" | % {
$hash[$_] = ($obj | SELECT -exp $_)
}
$hash
}
Disclaimer: I barely understand PowerShell so this is probably not as clean as it could be. But it works (for one level only).
Upvotes: 4
Reputation: 126732
Keith already gave you the answer, this is just another way of doing the same with a one-liner:
$psobject.psobject.properties | foreach -begin {$h=@{}} -process {$h."$($_.Name)" = $_.Value} -end {$h}
Upvotes: 39