Powershell Group-object array list - arrays

Two comma separated item added in array list and I would like to group them to count the total.
$list_distinct = [System.Collections.ArrayList]#()
$list_distinct.Add("Site A,Item A")
$list_distinct.Add("Site A,Item A")
$list_distinct.Add("Site A,Item B")
$list_distinct.Add("Site B,Item C")
$list_distinct.Add("Site B,Item D")
$list_distinct.Add("Site B,Item D")
Tried this:
$test = $list_distinct | Group-Object Values
The result shows Count (the whole total), Name(empty) and Group (the whole added items).
Any way to fix this? Or is there any better method?
Desired output example:
Site | Item | Count
Site A | Item A | 2
Site A | Item B | 1
Site B | Item C | 1
Site B | Item D | 2

Neither the ArrayList object nor its elements have a property Values. Non-existent properties are expanded to an empty result, so all of your values are grouped under the same (empty) name.
Change this
$list_distinct | Group-Object Values
into this
$list_distinct | Group-Object
and the problem will disappear.
For your desired output you will also need to split the values and create new (custom) objects:
$list_distinct | Group-Object | ForEach-Object {
$site, $item = $_.Name -split ','
New-Object -Type PSObject -Property #{
'Site' = $site
'Item' = $item
'Count' = $_.Count
}
} | Select-Object Site, Item, Count
The trailing Select-Object is to enforce field order since PowerShell hashtables aren't ordered by default.
In PowerShell v3 and newer you can simplify that to
$list_distinct | Group-Object | ForEach-Object {
$site, $item = $_.Name -split ','
[PSCustomObject]#{
'Site' = $site
'Item' = $item
'Count' = $_.Count
}
}
The trailing Select-Object isn't needed here, because the [PSCustomObject] type accelerator implicitly uses an ordered hashtable.

Related

Group two arrays of objects in powershell

I have two array of objects. I want to insert the corresponding extraProperty in the "car" array, if found. There may be several extraProperties or none. That is, I wanted to add, when an extraProperty was found for the respective car, an array with the list of extraproperties found.
Each extraProperties consists of an object with the following properties: Id, Name, Value.
Code:
param(
[Parameter(Mandatory=$false, Position=1, ValueFromPipeline=$false)]
[string]$Types,
[Parameter(Mandatory=$false, Position=2, ValueFromPipeline=$false)]
[string]$PathFile,
[Parameter(Mandatory=$false, Position=3, ValueFromPipeline=$false)]
[string]$PathPropertyFile
)
$profiles_list = Import-Csv $PathFile -Header Id, model, Type Delimiter ";"
$extraProperties_list = Import-Csv $PathPropertyFile -Header ProfileId,Name,Value -Delimiter ";" # Get-Content -Path $pathFile
foreach($p in $car_list) {
$Property = $property_list.Where({$_.Id -eq $p.Id}) | Select-Object -Property Name,Value
if(-Not (($null -eq $Property ) -And (#($Property ).Count -eq 0)) ) {
$p = $p | Add-Member -NotePropertyMembers #{Properties=$Property }
} else {
$p = $p | Add-Member -NotePropertyMembers #{Properties=#()}
}
}
Data sample:
PropertyFile.csv
Id | Name | Value
504953 | Example1 | Value1
504953 | Example2 | Value2
504955 | Example3 | Value3
CarFiles.csv
Id | Model | Type
504953 | Model1 | 3
504954 | Model1 | 0
504955 | Model3 | 3
The problem is that the code is not efficient. The car array reaches 200000 positions and where each position is an object with several properties and the properties array also reaches these values. The script takes endless hours to execute.
Any way to optimize the insertion of a new property within arrays?
You need to check if this is faster (we don't know how large the CSV files are), but you could do it like this:
For demo I'm using Here-Strings, but in real life you import the data from files:
$profiles_list = Import-Csv $PathFile -Delimiter ";"
$extraProperties_list = Import-Csv $PathPropertyFile -Delimiter ";"
Using your examples:
$profiles_list = #"
Id;Model;Type
504953;Model1;3
504954;Model1;0
504955;Model3;3
"# | ConvertFrom-Csv -Delimiter ';'
$extraProperties_list = #"
Id;Name;Value
504953;Example1;Value1
504953;Example2;Value2
504955;Example3;Value3
"# | ConvertFrom-Csv -Delimiter ';'
# get the headers from the Cars
$profileHeaders = $profiles_list[0].PsObject.Properties.Name
# get the new property names from the ExtraProperties Name column
$newHeaders = $extraProperties_list.Name | Where-Object {$profileHeaders -notcontains $_} | Select-Object -Unique
# add all these new properties to the $profiles_list, for now with $null values
$profiles_list | ForEach-Object {
foreach($prop in $newHeaders) {
$_ | Add-Member -MemberType NoteProperty -Name $prop -Value $null
}
}
# group the $extraProperties_list by the Id column and loop through these groups
$extraProperties_list | Group-Object Id | ForEach-Object {
$id = $_.Name
# get an array of profiles with matching Ids
$profiles = $profiles_list | Where-Object {$_.Id -eq $id}
# and fill in the blanks
foreach($item in $profiles) {
foreach($extra in $_.Group) {
$item.($extra.Name) = $extra.Value
}
}
}
# output on screen
$profiles_list
# output to new CSV file
$profiles_list | Export-Csv -Path 'X:\CompletedProfiles.csv' -Delimiter ';' -NoTypeInformation
Result on screen:
Id : 504953
Model : Model1
Type : 3
Example1 : Value1
Example2 : Value2
Example3 :
Id : 504954
Model : Model1
Type : 0
Example1 :
Example2 :
Example3 :
Id : 504955
Model : Model3
Type : 3
Example1 :
Example2 :
Example3 : Value3

Display all values in PSCustomObject array [duplicate]

Is it possible to display the results of a PowerShell Compare-Object in two columns showing the differences of reference vs difference objects?
For example using my current cmdline:
Compare-Object $Base $Test
Gives:
InputObject SideIndicator
987654 =>
555555 <=
123456 <=
In reality the list is rather long. For easier data reading is it possible to format the data like so:
Base Test
555555 987654
123456
So each column shows which elements exist in that object vs the other.
For bonus points it would be fantastic to have a count in the column header like so:
Base(2) Test(1)
555555 987654
123456
Possible? Sure. Feasible? Not so much. PowerShell wasn't really built for creating this kind of tabular output. What you can do is collect the differences in a hashtable as nested arrays by input file:
$ht = #{}
Compare-Object $Base $Test | ForEach-Object {
$value = $_.InputObject
switch ($_.SideIndicator) {
'=>' { $ht['Test'] += #($value) }
'<=' { $ht['Base'] += #($value) }
}
}
then transpose the hashtable:
$cnt = $ht.Values |
ForEach-Object { $_.Count } |
Sort-Object |
Select-Object -Last 1
$keys = $ht.Keys | Sort-Object
0..($cnt-1) | ForEach-Object {
$props = [ordered]#{}
foreach ($key in $keys) {
$props[$key] = $ht[$key][$_]
}
New-Object -Type PSObject -Property $props
} | Format-Table -AutoSize
To include the item count in the header name change $props[$key] to $props["$key($($ht[$key].Count))"].

Add new property based on a different objects property

I'm trying to search through one column in each row of the table. I would then like to add another value to the row based on the number being search.
This code produces the table:
$LUNSSummary = ($NY_LUNS) -split '\s+(?=LOGICAL UNIT NUMBER)' | foreach {
$Stringdata = $_.replace(':','=')
New-Object PSObject -Property $(ConvertFrom-StringData $Stringdata)
}
$LUNSSummary |
select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups' |
Format-Table -AutoSize
Then I have this code which can search using the "Logical Unit Number" and produce the desired output. In this example the -contains is 1029 from the above screenshot.
$data = $LUNS_in_Pools | Out-String
$pools = $data -replace ': +','=' -split "`r`n`r`n" |
% { New-Object -Type PSCustomObject -Property (ConvertFrom-StringData $_) } |
select -Property *,#{n='LUNs';e={$_.LUNs -split ', '}} -Exclude LUNs
$pools | ? { $_.LUNs -contains 1029 } | select -Expand 'Pool Name'
Which produces in this case "Pool 2". The result can be Pool 1-99.
I want to combine these two codes to search every "Logical Unit Number" and add the result to the end of the table in a 5th section/column "Pools".
EDIT
As requested, raw data:
$NY_LUNS before $LUNSSummary gets it: http://pastebin.com/5wrd51Lf
$LUNS_in_Pools raw data: http://pastebin.com/Zg9q6jhe
Desired Output: (Pool is obtained from "Logical Unit Number")
EDIT 2
This is now the closest to correct so far, it prints the same pool result every time.
$LUNSSummary =
($NY_LUNS) -split '\s+(?=LOGICAL UNIT NUMBER)' |
foreach { $Stringdata =
$_.replace(':','=')
New-Object PSObject -Property $(ConvertFrom-StringData $Stringdata)
}
$data = $LUNS_in_Pools | Out-String
$pools = $data -replace ': +','=' -split "`r`n`r`n" |
% { New-Object -Type PSCustomObject -Property (ConvertFrom-StringData $_) } |
select -Property *,#{n='LUNs';e={$_.LUNs -split ', '}} -Exclude LUNs
$poolProperty = #{Label="Pool";Expression={$pools | ? { $_.LUNs -contains [int]$_.'LOGICAL UNIT NUMBER'} | select -Expand 'Pool Name'}}
$LUNSSummary | select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups',$poolProperty
if I check the output of $pools | ? { $_.LUNs -contains [int]$_.'LOGICAL UNIT NUMBER'} | select -Expand 'Pool Name'
I only see one result. I'm thinking maybe it has to be looped some how?
From the guess of it you just need one more calculated property on the end there for 'Pool'. You already have, and tested, the logic. Just need to implement it.
$poolProperty = #{Label="Pool";Expression={
$lunID = $_.'LOGICAL UNIT NUMBER';
$pools | Where-Object{$_.LUNs -contains $lunID} |
Select-Object -Expand 'Pool Name'}
}
$LUNSSummary | select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups',$poolProperty
We take the LOGICAL UNIT NUMBER of the current item in the pipeline and save it so that we can start another to extract the match from the $pools object. As long as you luns are exclusive this would always return one Pool Name.
The above should work but I changed how $pools was created so it matched the logic of $LUNSSummary. I used here-strings for the raw data from your paste bin.
$LUNSSummary = ($NY_LUNS) -split '\s+(?=LOGICAL UNIT NUMBER)' |
foreach { $Stringdata =
$_.replace(':','=')
New-Object PSObject -Property $(ConvertFrom-StringData $Stringdata)
}
$pools = ($LUNS_in_Pools | Out-String) -split '\s+(?=Pool Name)' | ForEach-Object{
New-Object -Type PSCustomObject -Property (ConvertFrom-StringData ($_ -replace ":","=")) |
Select -Property *,#{n='LUNs';e={$_.LUNs -split ',\s*'}} -Exclude LUNs
}
$poolProperty = #{Label="Pool";Expression={
$lunID = $_.'LOGICAL UNIT NUMBER';
$pools | Where-Object{$_.LUNs -contains $lunID} |
Select-Object -Expand 'Pool Name'}
}
$LUNSSummary | select 'Name','LOGICAL UNIT NUMBER','State','LUN Capacity(Megabytes)','LU Storage Groups',$poolProperty
Looks like $LUNS_in_Pools was a newline delimited string. Piping to Out-String cleaned it up to remove the newlines and allow the regex/ConvertFrom-StringData to work.

Changing an array column name

I have an array that contains NotePoperties that needs to be modified. When reading the array I need to do some code to figure out what the correct title for the array column should be.
This code is ready and works fine:
$Permissions | Get-Member | ? MemberType -EQ NoteProperty | % {
$Column = $Permissions.($_.Name)
$GroupResult = Switch ($Column[1]) {
'GROUP' {$Settings[0].Group + ' '}
'' {break}
Default {$group + ' '}
}
$SiteResult = Switch ($Column[0]) {
'Site' {$Settings[0].Code + ' '}
'' {break}
Default {$site + ' '}
}
$Result = ($GroupResult + $SiteResult + $_.Name).Trim()
}
What I'm now trying to do is update/modify the existing title with the generated $Result string from above:
$_.Name = $Result
When doing this, PowerShell throws the error 'Name' is a ReadOnly property..
What is the best way of changing the title of the array? I also need to remove line 2 and line 3, but I can do that afterwards.
Example of the $Permissions array where I'm trying to updated Plant manager to Belgium Awesome Plant manager:
Plant manager | Sales man | Warehouseman
-------------- ---------- -------------
Site | |
Group | Group |
Stuff | Stuff | Stuff
Thank you for your help.
Agreed that this is over-engineered, however you can modify the object in place.
Assuming your object array is $permissions, you can iterate over it, add a member of $result with value from property Plant manager and then remove the Plant manager property:
$permissions | % {
$_ | add-member "$result" -NotePropertyValue $_."Plant Manager"
$_.psobject.properties.remove("Plant Manager")
}
One option is to use Add-Member to add a new NoteProperty with the desired name, and copy the value from the existing one.
That being said, it seems like you're over-engineering this quite a bit.
How about a calculated property instead:
$Permissions = $Permissions |Select-Object #{Name="Belgium Awesome Plant Manager"; Expression = {$_.'Plant Manager'} },'Sales man','Warehouseman'
The ReadOnly attribute of the object property is going to be dictated by the object type. One way around this is to run the object thorugh Select *. This will change the object type to either a PSSelectedObject or PSCustomObject, depending on your PS version, and the property should lose the ReadOnly attribute in the process:
$Permissions | Get-Member | ? MemberType -EQ NoteProperty | Select * |% {
Instead of making a duplicate value with Add-Member you could also create an AliasProperty
Get-ChildItem c:\temp | Add-Member -MemberType AliasProperty -Name Measurement -Value Length -PassThru | select Measurement
($json | ConvertTo-csv).replace('"name"','"ServiceAccountName"') | ConvertFrom-csv | ConvertTo-Json
$json is an existing json array with a column name "name". I convert it to a csv file wherein the first line defines the columns and replace the "name" with "ServiceAccountName" and convert it back from csv to json

Powershell Hash Table Grouping

I have a CSV like below:
location,id
loc1,1234
loc1,1235
loc1,1236
Running $a = Import-CSV C:\File.csv | Group-Object "location" I get the following output:
Count Name Group
----- ---- -----
3 loc1 {#{location=loc1; id=1234}, #{location=loc1; id=1235), #{location=loc1, id=1236}}
I would like to add all ID's to a single group (Using Add-QADGroupMember) but I can't figure out how to get a group of ID's for $loc1. It seems to be be grouping them correctly but I can't seem to parse the output into a single group. E.g $loc1 = 1234,1235,1236 that I can loop through.
Any suggestions would be appreciated!
Group-Object doesn't handle hashtables well, since the keys aren't real properties.
Assuming:
$csv = Import-CSV C:\File.csv
You should be able to do, for example:
$ids = $csv | %{ $_.id }
to get an array of the ID values. You'd probably want to pipe through Get-Unique for location.
If you wanted to get the location for a single ID quickly:
$location = $csv | ?{ $_.id -eq 42 } | %{ $_.location }
If you wanted to get an array of all IDs for a single location quickly (I think this is what you want):
$loc1 = $csv | ?{ $_.location -eq 'loc1' }
For reference, if you wanted to get a hashtable mapping each location to an array of IDs:
$groups = $csv | %{ $_.location } | &{
begin
{
$hash = #{}
}
process
{
$location = $_.location
$hash[$location] = $csv | ?{ $_.location -eq $location }
}
end
{
$hash
}
}
A bit tricky, but this will do it:
Import-Csv C:\File.csv | Group-Object "location" | %{Set-Variable ($_.Name) ($_.Group | Select-Object -ExpandProperty id)}
After running that, $loc1, $loc2, etc. will be arrays of all the ids for each location.
And yet another option:
(Import-Csv c:\foo.csv | Group Location -AsHashTable).Loc1 | Foreach {$_.id}
And if you're on V3, you can do this:
(Import-Csv c:\foo.csv | Group Location -AsHashTable).Loc1.Id

Resources