I would like to convert this array of objects
Name CIDR
---- ----
sdc-MO 10.92.18.136/20
sdc-RM 10.77.6.34/20
into a single [PSCustomObject]
sdc-MO sdc-RM
------- -------
{10.92.18.136/20} {10.77.6.34/20}
Please suggest any easy way.. Thanks
Add each object to a hashtable or other dictionary dictionary type, then use the dictionary to create the object (each entry will become a separate property):
$array = #(
[pscustomobject]#{ Name = 'sdc-MO'; CIDR = '10.92.18.136/20' }
[pscustomobject]#{ Name = 'sdc-RM'; CIDR = '10.77.6.34/20' }
)
# Prepare a new dictionary to hold the properties
$newProperties = [ordered]#{}
foreach($inputObject in $array){
# If we don't already have a property with the given name,
# create a new entry in the dictionary
if(-not $newProperties.Contains($inputObject.Name)){
$newProperties.Add($inputObject.Name, #())
}
# Add the `CIDR` value to the corresponding property name
$newProperties[$inputObject.Name] += $inputObject.CIDR
}
$newObject = [pscustomobject]$newProperties
$newObject will be like what you described in the question:
PS C:\> $newObject
sdc-MO sdc-RM
------ ------
{10.92.18.136/20} {10.77.6.34/20}
Related
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
I have file with multiple sets of data, as it:
metadata :
mxRecords :
name : dev-dev
arecords : {#{ipv4Address=127.0.0.1}, #{ipv4Address=127.0.0.2}, #{ipv4Address=127.0.0.3}, #{ipv4Address=127.0.0.4}}
I need find sets of data, where in arecords contains ipv4Address what I need.
How can I get access to values of ipv4Address?
You have nested custom objects rather than hash tables. You can use the member access operator . to reference nested properties.
Example 1: From one object, find the arecords object that contains the target IP address
# Record example matching your post
$obj = [pscustomobject]#{
metadata = ''
mxRecords = ''
name = 'dev-dev'
arecords = [pscustomobject]#{ipv4Address='127.0.0.1'},
[pscustomobject]#{ipv4Address='127.0.0.2'},
[pscustomobject]#{ipv4Address='127.0.0.3'},
[pscustomobject]#{ipv4Address='127.0.0.4'}
}
$targetIP = '127.0.0.3'
$obj.arecords | Where ipv4Address -eq $targetIP
# Output
ipv4Address
-----------
127.0.0.3
Example 2: From multiple objects, find the object that contains an arecords array with the target IP
$obj = [pscustomobject]#{
metadata = ''
mxRecords = ''
name = 'dev-dev'
arecords = [pscustomobject]#{ipv4Address='127.0.0.1'},
[pscustomobject]#{ipv4Address='127.0.0.2'},
[pscustomobject]#{ipv4Address='127.0.0.3'},
[pscustomobject]#{ipv4Address='127.0.0.4'}
},
[pscustomobject]#{
metadata = ''
mxRecords = ''
name = 'prod-prod'
arecords = [pscustomobject]#{ipv4Address='127.0.0.10'},
[pscustomobject]#{ipv4Address='127.0.0.20'},
[pscustomobject]#{ipv4Address='127.0.0.30'},
[pscustomobject]#{ipv4Address='127.0.0.40'}
}
$targetIP = '127.0.0.10'
$obj | Where {$_.arecords.ipv4Address -contains $targetIP}
# Output
metadata mxRecords name arecords
-------- --------- ---- --------
prod-prod {#{ipv4Address=127.0.0.10}, #{ipv4Address=127.0.0.20}, #{ipv4Address=127.0.0.30}, #{ipv4Address=127.0.0.40}}
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" }
I have 2 PowerShell variables that include data in the following format. For the first table:
UserName Department
-------- ----------
X#Y.com IT
and data in the following format in the second table:
Country MobileNumber
------- ------------
Singapore +65 8xxxxxxx
and the other variable is the same with different column names and different content. I want to merge the 2 variables to have them in one single variable in that would be in the following format:
UserName Department Country MobileNumber
-------- ---------- ------- ------------
Update:
The result of Ansgar commnet, generate it in the following format:
UserName Department Country MobileNumber
-------- ---------- ------- ------------
{x#y.com, z#y.com} {IT, Sales} {Singapore, Singapore} the same here
Assuming that you have individual objects in your two variables you could construct new objects from them like this:
$obj = New-Object -Type PSObject -Property #{
'UserName' = $obj1.UserName
'Department' = $obj1.Department
'Country' = $obj2.Country
'MobileNumber' = $obj2.MobileNumber
}
If you have arrays of objects (which, judging from your updated question, is the case) you need to build the objects in a loop. Note that this assumes an equal number of objects in both variables. Note also that you MUST ensure that both arrays are in the correct order, unless you have some criteria by which you can match objects from one array to the corresponding object from the other array.
$obj = for ($i=0; $i -lt $obj1.Count; $i++) {
New-Object -Type PSObject -Property #{
'UserName' = $obj1[$i].UserName
'Department' = $obj1[$i].Department
'Country' = $obj2[$i].Country
'MobileNumber' = $obj2[$i].MobileNumber
}
}
I would create a new object and map the properties in a hashtable like this:
First, you have your variables with the data, in this example it is
$Variable1
$Variable2
Then we want to make a Hashtable, this will map the data in each of these two variables to a property in our new object, so we map the two properties Username and Department from the first variable and the Country and the MobileNumber from the second variable.
$properties = #{
'Username'=$Variable1.UserName;
'Department'=$Variable1.Department;
'Country'=$Variable2.Country;
'MobileNumber'=$Variable2.MobileNumber;}
Lastly we create a new object, in this example I call the object $User. We tell this object to contain the properties for our Hashtable:
$User = New-Object -TypeName psobject -Property $properties
I am trying to find the matching names in two different types of Powershell objects
$Object1 has two properties - Name (string), ResourceID (uint32)
$object2 has one noteproperty - Name (system.string)
This gives me a list of the matching names but I also want the corresponding resourceID property from $object1.
$computers = Compare-Object $Object1.name $WSD_CM12 | where {$_.sideindicator -eq "=>"} | foreach {$_.inputobject}
These are big objects with over 10,000 items so I'm looking for the most efficient way to accomplish this.
If I'm understanding what you're after, I'd start by creating a hash table from your Object1 collection:
$object1_hash = #{}
Foreach ($object1 in $object1_coll)
{ $object1_hash[$object1.Name] = $object1.ResourceID }
Then you can find the ResourceID for any given Object2.name with:
$object1_hash[$Object2.Name]
Test bed for creating hash table:
$object1_coll = $(
New-Object PSObject -Property #{Name = 'Name1';ResourceID = 001}
New-Object PSObject -Property #{Name = 'Name2';ResourceID = 002}
)
$object1_hash = #{}
Foreach ($object1 in $object1_coll)
{ $object1_hash[$object1.Name] = $object1.ResourceID }
$object1_hash
Name Value
---- -----
Name2 2
Name1 1
Alternative method:
# Create sample list of objects with both Name and Serial
$obj1 = New-Object -Type PSCustomObject -Property:#{ Name = "Foo"; Serial = "1234" }
$obj2 = New-Object -Type PSCustomObject -Property:#{ Name = "Cow"; Serial = "4242" }
$collection1 = #($obj1, $obj2)
# Create subset of items with only Name
$objA = New-Object -Type PSCustomObject -Property:#{ Name = "Foo"; }
$collection2 = #($objA)
#Everything above this line is just to make sample data
# replace $collection1 and $collection2 with $Object1, $WSD_CM12
# Combine into one list
($collection1 + $collection2) |
# Group by name property
Group-Object -Property Name |
# I only want items that exist in both
Where { $_.Count -gt 1 } |
# Now give me the object
Select -Expand Group |
# And get the properties
Where { $_.Serial -ne $null }