I have an array of objects, which have the property of p1, p2, p3, p4.
$objectArray = [PSCustomObject]#{
p1 = "Value1"; p2 = "Value2"; p3 = "Value3"; p4 = "Value4"
},[PSCustomObject]#{
p1 = "Value5"; p2 = "Value6"; p3 = "Value7"; p4 = "Value8"
}
And I have a property-name-mapping hashtable, for example
$propertyReplacements = #{
"p1" = "x1"
"p2" = "y1"
}
How to select only the properties which exist in the keys of hash table $propertyReplacements; and rename the property name to the values in the hash table?
The result will be
[PSCustomObject]#{
x1 = "Value1"; y1 = "Value2";
},[PSCustomObject]#{
x1 = "Value5"; y1 = "Value6";
}
The properties filtering can be
$objectArray | select -Property #($propertyReplacements.Keys) |
....
The following uses an ordered dictionary and casting [pscustomobject] to create new objects, I really don't think Select-Object is the proper tool for this.
# use a temporary ordered dictionary
$tmp = [ordered]#{}
# enumerate each object
foreach($obj in $objectArray) {
# store the PSMemberInfoIntegratingCollection for later access
$properties = $obj.PSObject.Properties
# enumerate the hash keys
foreach($key in $propertyReplacements.Keys) {
# if the hash key exists as property Name in this object
if($prop = $properties.Item($key)) {
# create a new property Name using the Value of the hashtable
# and keep the current Value of the value
$tmp[$propertyReplacements[$prop.Name]] = $prop.Value
}
}
# create a new object casting this type accelerator
[pscustomobject] $tmp
# and clear the dictionary for the next object
$tmp.Clear()
}
Here is another solution
foreach($k in $propertyReplacements.Keys) {
if ($k -ne $propertyReplacements.$k) {
$objectArray = $objectArray | select *,{n=$propertyReplacements.$k; $e={$_.$k}}
$objectArray = $objectArray | select -ExcludeProperty $k
}
}
# $objectArray has updated property names now
Related
I have few [pscustomobject] objects that can have not all properties.
For example:
PS> $1 = [pscustomobject]#{ A='a1'; B='b1' }
PS> $2 = [pscustomobject]#{ A='a2'; C='c2' }
And I try to display all properties with Format-Table like this:
PS> $1,$2 | Format-Table
A B
- -
a1 b1
a2
PS> $2,$1 | Format-Table
A C
- -
a2 c2
a1
But every time it displays only properties from first object in collection.
I want to display all properties like if I set -Property argument explicitly.
PS> $1,$2 | Format-Table -Property A,B,C
A B C
- - -
a1 b1
a2 c2
Setting -Property argument is good if:
All set of properties is known in advance
Collection is small and I can get all properties with Get-Member -MemberType Properties
But I have a huge collection (above 10000 objects) with unknown properties so I need help with it.
REMARK: Format-Table will be used only for small slices (10-100 elements).
For that, you can use below function to merge all properties into the first object:
function Complete-ObjectHeaders {
# function to add properties to the first item in a collection of PSObjects
# when this object is missing properties from items further down the array.
# you may need this if you have such a collection and want to export it
# to Csv, since Export-Csv (and also Format-Table) only looks at the FIRST
# item to create the csv column headers.
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[PSObject[]]$Collection,
[int]$MaxItemsToTest = -1, # < 0 --> test all items in the collection
[switch]$SortHeaders
)
# Try and find all headers by looping over the items in the collection.
# The headers will be captured in the order in which they are found.
if ($MaxItemsToTest -gt 0) {
$MaxItemsToTest = [math]::Min($MaxItemsToTest, $Collection.Count)
$headers = for($i = 0; $i -lt $MaxItemsToTest; $i++) {
($Collection[$i].PSObject.Properties).Name
}
$headers = $headers | Select-Object -Unique
}
else {
$headers = $Collection | ForEach-Object {($_.PSObject.Properties).Name} | Select-Object -Unique
}
if ($SortHeaders) { $headers = $headers | Sort-Object }
# update the first object in the collection to contain all headers
$Collection[0] = $Collection[0] | Select-Object $headers
,$Collection
}
Use like this:
$1 = [pscustomobject]#{ A='a1'; B='b1' }
$2 = [pscustomobject]#{ A='a2'; C='c2' }
# just output to console
Complete-ObjectHeaders -Collection $1,$2 | Format-Table -AutoSize
# or capture the merged array of objects in a new variable you can save as CSV file for instance
$merged = Complete-ObjectHeaders -Collection $1,$2
$merged | Export-Csv -Path 'D:\Test\Merged.csv' -NoTypeInformation
Output:
A B C
- - -
a1 b1
a2 c2
Thanks #theo for the answer.
I used it to write my own version of a function that supports pipelining.
function Expand-Properties {
[Cmdletbinding()]
param(
[Parameter(ValueFromPipeline)]
$InputObject,
[Parameter()]
[Alias('All')]
[switch]
$ExpandAll,
[Parameter()]
[switch]
$SortHeaders
)
begin {
$collection = [System.Collections.ArrayList]::new()
$properties = [System.Collections.ArrayList]::new()
}
process {
[void]$collection.Add($InputObject)
$properties.AddRange((($InputObject.PSObject.Properties).Name))
}
end {
if ($SortHeaders) {
$properties = $properties | Sort-Object -Unique
} else {
$properties = $properties | Select-Object -Unique
}
if ($ExpandAll) {
for ($i = 0; $i -lt $collection.Count; ++$i) {
$collection[$i] = $collection[$i] | Select-Object -Property $properties
}
} else {
$collection[0] = $collection[0] | Select-Object -Property $properties
}
$collection
}
}
EXAMPLE:
PS> $1 = [pscustomobject]#{ A='a1'; B='b1' }
PS> $2 = [pscustomobject]#{ A='a2'; C='c2' }
PS> $1, $2 | Expand-Properties
A B C
- - -
a1 b1
a2 c2
I'm not very good at powershell and I need help.
I am trying to combine properties of objects. For two objects, everything is fine. This is looks like.
$obj1 = [PSCustomObject]#{
'ip' = '10.10.10.11'
't1' = 'show'
't2' = 'ab'
}
$obj2 = [PSCustomObject]#{
'ip' = '10.10.10.11'
't1' = 'me'
't2' = 'cd'
}
foreach ($prop in $obj2 | Get-Member -MemberType NoteProperty | Where-Object {$_.name -ne 'ip'} | select -ExpandProperty name)
{
$obj1.$prop += $obj2.$prop
}
$obj1
Result:
ip t1 t2
-- -- --
10.10.10.11 showme abcd
But I can't do this if many objects in an array. How to do it?
Example arrays:
Array1:
ip t1 t2
-- -- --
10.10.10.11 show ab
10.10.10.12 show ab
Array2:
ip t1 t2
-- -- --
10.10.10.11 me cd
10.10.10.12 me cd
Basically, all you need is to add an outer loop that iterates over the array elements.
Additionally, you can use the hidden .psobject.Properties member to streamline your solution:
# Sample arrays
$array1 = [pscustomobject]#{ 'ip' = '10.10.10.11'; 't1' = 'show1'; 't2' = 'ab1' },
[pscustomobject]#{ 'ip' = '10.10.10.12'; 't1' = 'show2'; 't2' = 'ab2' }
$array2 = [pscustomobject]#{ 'ip' = '10.10.10.11'; 't1' = 'me1'; 't2' = 'cd1' },
[pscustomobject]#{ 'ip' = '10.10.10.12'; 't1' = 'me2'; 't2' = 'cd2' }
# Assuming all objects in the arrays have the same property structure,
# get the list of property names up front.
$mergeProperties = $array1[0].psobject.Properties.Name -ne 'ip'
# Loop over the array elements in pairs and merge their property values.
$i = 0
[array] $mergedArray =
foreach ($o1 in $array1) {
$o2 = $array2[$i++]
foreach ($p in $mergeProperties) {
$o1.$p += $o2.$p
}
$o1 # output the merged object
}
Outputting $mergedArray then yields:
ip t1 t2
-- -- --
10.10.10.11 show1me1 ab1cd1
10.10.10.12 show2me2 ab2cd2
Note:
As in your attempt, one of the original objects is modified in place.
The assumption is that matching objects in the two arrays are in the same position, respectively (merge the first object from the first array with the first object from the second, ....).
How i can create array like:
$a -> [1] ->
[1] = value1
[2] = value2
.................
[n] = valueN
[2] ->
[1] = value1
[2] = value2
.................
[n] = valueN
and so on.
Thank you
i have tried like this:
$b = #{}
$b[0][0] = 1
$b[0][1] = 2
$b[0][2] = 3
$b[1][0] = 4
$b[1][1] = 5
$b[1][2] = 6
$b
But it doesn't give the required output
I think this has been posted multiple times, but simply declare the array and give it values:
[array]$1 = "value1","value2"
[array]$2 = "value1","value2"
[array]$a = $1,$2
$a[0][0]
will output -> value1 from the first
Please note that declaring the array with [array] is for clarifiying, it is not necessary. If you add comma-seperated values the variable automatically is an array.
EDIT:
What you have tried is a hashtable. A hashtable contains a key and a value. An array is only a list of values. A hashtable is created as follows:
$b = #{
1 = #{
1 = "value1"
2 = "value2"
}
2 = #{
1 = "value1"
2 = "value2"
}
3 = "value3"
}
$b
As you can see, you can add as many sublevels as you like. To show the value of the first "value1" type:
$b[1].1
You could use the class approach as well I would prefer:
Add-Type -AssemblyName System.Collections
# Create class with needed members
class myListObject {
[int]$myIndex
[System.Collections.Generic.List[string]]$myValue = #()
}
# generic list of class
[System.Collections.Generic.List[myListObject]]$myList = #()
#create and add objects to generic list
$myObject = [myListObject]::new()
$myObject.myIndex = 1
$myObject.myValue = #( 'value1', 'value2' )
$myList.Add( $myObject )
$myObject = [myListObject]::new()
$myObject.myIndex = 2
$myObject.myValue = #( 'value3', 'value4' )
$myList.Add( $myObject )
# search items
$myList | Where-Object { $_.myIndex -eq 1 } | Select-Object -Property myValue
$myList | Where-Object { $_.myValue.Contains('value3') } | Select-Object -Property myIndex
The Windows Powershell in Action answer.
$2d = New-Object -TypeName 'object[,]' -ArgumentList 2,2
$2d.Rank
#2
$2d[0,0] = "a"
$2d[1,0] = 'b'
$2d[0,1] = 'c'
$2d[1,1] = 'd'
$2d[1,1]
#d
# slice
$2d[ (0,0) , (1,0) ]
#a
#b
# index variable
$one = 0,0
$two = 1,0
$pair = $one,$two
$2d[ $pair ]
#a
#b
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 }
how to get unique value from an array of hashtable in powershell?
$h1 = #{a=1;b=2};
$h2 = #{a=3;b=4};
$h3 = #{a=1;b=2};
$h = $h1,$h2,$h3
I want to remove duplicate value ($h1 or $h3) from $h.
thanks!
You can try something like this:
$h | select #{ Expression = { "$($_.Keys):$($_.Values)" }; Label ="AsString" }, #{ Expression ={$_}; Label = "Hash" } -unique
| select -expand Hash