PowerShell: Splitting an Array into two columns - powershell

I have an array with the following values:
| Firstname | Lastname | Username |
|-----------|------------------|----------|
| person1 | person1_lastname | p1 |
| person2 | person2_lastname | p2 |
| person3 | person3_lastname | p3 |
| person4 | person4_lastname | p4 |
This is the code that produces the above results:
$finalUsers = foreach($person in $excludedUsers) {
if ($person.Username -notin $ausLunchJobs.AssigneeUser -and $person.Username -notin $ausLunchJobs.AssigneeUser2) {
$person | Select-Object Firstname, Lastname, Username
}
}
I want to split that array into two columns and pair the Username data together.
Ideal output:
| Username | Username2 |
|----------|-----------|
| p1 | p2 |
| p3 | p4 |
Any guidance on how I can achieve something like this?
TIA

Create 1 new object per row you want displayed in the table:
$userPairs = for($i = 0; $i -lt $finalUsers.Count; $i += 2){
$finalUsers[$i] |Select-Object Username,#{ Name='Username2'; Expression={ $finalUsers[$i+1].username } }
}
Result:
PS ~> $userPairs |Format-Table
Username Username2
-------- ---------
p1 p2
p3 p4

Related

PowerShell: Incremental Counter in Foreach Loop

I have a foreach loop that iterates over an Array and calls a function which also has another foreach loop inside with an incremental counter, however it doesn't seem to be working as expected?
Array contents:
| Username | Username2 |
|----------|-----------|
| p1 | p2 |
| p3 | p4 |
Code:
function insertIntoLunchJobs($arrayOfRows) {
$counter = 1
foreach ($i in $arrayOfRows) {
$i
$counter++
$counter
}
}
Output:
| Username | Username2 |
|----------|-----------|
| p1 | p2 |
| 2 | |
| p3 | p4 |
| 2 | |
Desired result:
| Username | Username2 |
|----------|-----------|
| p1 | p2 |
| 2 | |
| p3 | p4 |
| 3 | |
Any ideas?
TIA
I'm literally copy pasting your code. I don't see any errors here:
$arr=#'
Username,Username2
p1,p2
p3,p4
'#|ConvertFrom-Csv
function insertIntoLunchJobs($arrayOfRows) {
$counter = 1
foreach ($i in $arrayOfRows) {
$i
$counter++
$counter
}
}
insertIntoLunchJobs -arrayOfRows $arr

Selecting unique values per rows in pscustomobject

I have this custom object:
**Id | Name | User**
1 | A | {Joe, Joe, Chloe, Cindy}
2 | B | {Joe, Andy, Andy, Cindy, Cindy}
3 | C | {Joe, Joe, Chloe, Chloe, Andy, Andy}
I need to sort unique users for each individual object like below:
**Id | Name | User**
1 | A | {Joe, Chloe, Cindy}
2 | B | {Joe, Andy, Cindy}
3 | C | {Joe, Chloe, Andy}
I need to output the ID or Name after sorting.
The closest I could get was to run a | sort-object -unique, but it doesn't work as I was not able to pull the individual IDs/Names.
You can use "Select-Object" with a hashtable (also known as a calculated property):
$objects | Select-Object Id,Name,#{ Name = "User"; Expression = { $_.User | Select-Object -Unique } }

Merge two CSV files while adding new and overwriting existing entries

I have a configuration.csv that holds a template data like this:
| path | item | value | type |
|------------|-------|--------|------|
| some/path | item1 | value1 | ALL |
| some/path | item2 | UPDATE | ALL |
| other/path | item1 | value2 | SOME |
and customization.csv that has service specific configuration:
| path | item | value | type |
|------------|-------|--------|------|
| some/path | item2 | value3 | ALL |
| new/path | item3 | value3 | SOME |
My goal is to merge them and end up with something like this:
| path | item | value | type |
|------------|-------|--------|------|
| some/path | item1 | value1 | ALL |
| some/path | item2 | value3 | ALL |
| other/path | item1 | value2 | SOME |
| new/path | item3 | value3 | SOME |
This should add any new entries and update any existing ones. No one column can be used for unique identification - both path and item needs to be combined, as they are guaranteed to be unique.
I suggest to use Compare-Object and as the values from customization.csv shall persist use this files values as -ReferenceObject
## Q:\Test\2019\03\01\SO_54948111.ps1
$conf = Import-Csv '.\configuration.csv'
$cust = Import-Csv '.\customization.csv'
$NewData = Compare-Object -ref $cust -diff $conf -Property path,item -PassThru -IncludeEqual|
Select-Object -Property * -ExcludeProperty SideIndicator
$NewData
$NewData |Export-Csv '.\NewData.csv' -NoTypeInformation
Sample output
> Q:\Test\2019\03\01\SO_54948111.ps1
path item value type
---- ---- ----- ----
some/path item2 value3 ALL
some/path item1 value1 ALL
other/path item1 value2 SOME
new/path item3 value3 SOME
After a lot of searching, I figured the easiest way to manipulate the entries without recreating the managing framework will be through hashtable. During the process I had to account for two edge cases:
additional commas in the values
empty values
The final solution I got is this:
$configuration = Import-Csv .\configuration.csv
$customization = Import-Csv .\customization.csv
$merged = New-Object System.Collections.ArrayList
$hashTable = #{}
#initializing the hashTable with the defaults
foreach ($entry in $configuration)
{
$hashTable[$entry.path + ',' + $entry.item] = $entry.value + ',' + $entry.type
}
#updating the hashTable with customization that add or overwrite existing entries
foreach ($entry in $customization)
{
$hashTable[$entry.path + ',' + $entry.item] = $entry.value + ',' + $entry.type
}
#the regex handles multiple commas and empty values.
#It returns an empty string before and after group so we start from 1
foreach ($key in $hashTable.keys)
{
$psobject = [PSCustomObject]#{
path = ($key -split '(.*),(.*)')[1]
item = ($key -split '(.*),(.*)')[2]
value = ($hashTable[$key] -split '(.*),(.*)')[1]
type = ($hashTable[$key] -split '(.*),(.*)')[2]
}
[void] $merged.Add($psobject)
}
Write-Output $merged
Once imported, I transform the configuration.csv into hashTable with keys comprised of path and value. I then do the same with customization.csv using the same hashTable which overwrites any existing key values or add them as new.
The third loop converts the hashTable to PSCustomObject similar to what Import-Csv does. I split each of the key and value attributes while accounting for multiple commas and also empty values.
NOTE: the regex will split on the last occurrence of the separator (here it's comma, but you can select anything, really). If you want to split on the first, you can use (.*?),(.*). In my case only the value column could contain an instance of the separator.
If the CSV had a unique column, then a solution similar to this answer could've been used.
Another alternative is to set the keys to be the sum of all columns, and this will filter out any duplicates in the CSV, but the splitting can get tricky, depending on the values in the columns.
Your idea 'using the same hashTable which overwrites any existing key values or add them as new.' will only work if the path, item is unique on each side as you will also overwrite any duplicates...
Consider this Join-Object cmdlet.
$configuration = ConvertFrom-SourceTable '
| path | item | value | type |
|------------|-------|--------|------|
| some/path | item1 | value1 | ALL |
| some/path | item2 | UPDATE | ALL |
| other/path | item1 | value2 | SOME |
| other/path | item1 | value3 | ALL |
'
$customization= ConvertFrom-SourceTable '
| path | item | value | type |
|------------|-------|--------|------|
| some/path | item2 | value3 | ALL |
| new/path | item3 | value3 | SOME |
| new/path | item3 | value4 | ALL |
'
Using the Merge-Object, alias Merge, proxy command (see help):
$configuration | Merge $customization -on path, item
path item value type
---- ---- ----- ----
some/path item1 value1 ALL
some/path item2 value3 ALL
other/path item1 value2 SOME
other/path item1 value3 ALL
new/path item3 value3 SOME
new/path item3 value4 ALL

Powershell Hashtable Export to CSV

I am trying to create a CSV export that contains all rows in the data spreadsheet that the IDs from the search spreadsheet show up in.
I have managed to create the searching element through PowerShell now but am having trouble exporting the data into the CSV format.
Below are some example tables, the actual data has up to 8 values (including ID column), but only the first three are guaranteed to be filled.
Data Spreadsheet
+------+---------+---------+---------+---------------------+
| ID | Value 1 | Value 2 | Value 3 | Value 4 |
+------+---------+---------+---------+---------------------+
| 1234 | London | Serial1 | HP | Laptop User |
| 2345 | Moscow | Serial7 | | HR Application |
| 1234 | London | Serial9 | | Finance Application |
| 3456 | Madrid | Serial4 | HP | Laptop User |
+------+---------+---------+---------+---------------------+
Search Spreadsheet
+------+
| ID |
+------+
| 1234 |
| 2345 |
+------+
Desired Result
+------+---------+---------+---------+---------------------+
| ID | Value 1 | Value 2 | Value 3 | Value 4 |
+------+---------+---------+---------+---------------------+
| 1234 | London | Serial1 | HP | Laptop User |
| 2345 | Moscow | Serial7 | | HR Application |
| 1234 | London | Serial9 | | Finance Application |
+------+---------+---------+---------+---------------------+
Below is the current code that I have with the attempts to export to CSV removed.
$finalValues = #{}
$users = Import-Csv "SEARCH.csv"
$data = Import-Csv "DATA.csv" | Group-Object -property ID -AsHashTable
foreach ($user in $users)
{
If ($data.Contains($user.ID))
{
#write-output $data[$user.ID].ID
$finalValues.Add($data[$user.ID].ID, $data[$user.ID])
}
}
The following two commands (ran after the rest of the script has executed) have the below output.
$finalValues.Values
ID : 1234
Value 1 : London
Value 2 : Serial 1
Value 3 : HP
Value 4 :
Value 5 :
Value 6 :
Value 7 : Laptop User
ID : 2345
Value 1 : Moscow
Value 2 : Serial7
Value 3 :
Value 4 :
Value 5 :
Value 6 :
Value 7 : HR Application
ID : 1234
Value 1 : London
Value 2 : Serial9
Value 3 :
Value 4 :
Value 5 :
Value 6 :
Value 7 : Finance Application
$finalValues
{1234, 1234} {#{ID=1234; Value 1=London; Value 2=Serial1; Value 3=HP; Value 4=; Value 5=; Value 6=; Value 7=Laptop User}, #{ID=1234; Value 1=London; Value 2=Serial 9... ; Value 7 =Finance Application}}
2345 {#{ID=2345; Value 1=Moscow; Value 2=Serial7; Value 3=; Value 4=; Value 5=; Value 6=; Value 7=HR Application}}
When exporting to CSV with the following command I get the following result:
$finalValues | export-csv -Path test.csv -NoTypeInformation
+------------+-------------+----------------+--------------------------------------------+----------------------------------------------+---------------+-------+
| IsReadOnly | IsFixedSize | IsSynchronized | Keys | Values | SyncRoot | Count |
+------------+-------------+----------------+--------------------------------------------+----------------------------------------------+---------------+-------+
| FALSE | FALSE | FALSE | System.Collections.Hashtable+KeyCollection | System.Collections.Hashtable+ValueCollection | System.Object | 14 |
+------------+-------------+----------------+--------------------------------------------+----------------------------------------------+---------------+-------+
When exporting to CSV with the following command I get the following result:
$finalValues.Values | export-csv -Path test.csv -NoTypeInformation
+-------+------------+-------------+---------------+----------------+
| Count | IsReadOnly | IsFixedSize | SyncRoot | IsSynchronized |
+-------+------------+-------------+---------------+----------------+
| 1 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 3 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 2 | FALSE | FALSE | System.Object | FALSE |
| 2 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 2 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
| 1 | FALSE | FALSE | System.Object | FALSE |
+-------+------------+-------------+---------------+----------------+
#BenH's answer is clearly a better way to implement this, but I just wanted to explain what the issue was with your original code. The problem is that $data is a HashTable mapping a string (the user ID) to an array (actually it's a Collection<PSObject> but for our purposes it behaves the same). Even in the case of ID '2345' where there is only one matching record, $data still stores it as an array with one element:
PS> $data['2345'].GetType().Name
Collection`1
PS> $data['2345'].Count
1
PS> $data['2345'] # Returns the array of values
ID : 2345
Value 1 : Moscow
Value 2 : Serial7
Value 3 :
Value 4 : HR Application
PS> $data['2345'][0] # Returns the first element of the array of values
ID : 2345
Value 1 : Moscow
Value 2 : Serial7
Value 3 :
Value 4 : HR Application
Thus, when this line executes...
$finalValues.Add($data[$user.ID].ID, $data[$user.ID])
...you are adding a new item to $data where both the key and the value are arrays. This is why the output of piping $finalValues or $finalValues.Values to Export-Csv behaves as if the values are arrays; it's because they are.
To fix this, when accessing items in $data we'll need an inner loop to "unwrap" each value. Also, we can't use a HashTable for $finalValues because you're using ID as the key but there are duplicate IDs ('1234') in the results. Since all we need is a flat list of records to eventually pass to Export-Csv, we can just use an array instead. Here's some modified code...
$finalValues = #() # Cannot be a HashTable because there may be multiple results with the same ID
$users = Import-Csv "SEARCH.csv"
$data = Import-Csv "DATA.csv" | Group-Object -property ID -AsHashTable
foreach ($user in $users)
{
If ($data.Contains($user.ID))
{
# "Unwrap" the array stored at $data[$user.ID]
foreach ($dataRecord in $data[$user.ID])
{
$finalValues += $dataRecord
}
}
}
$finalValues | Export-Csv -Path test.csv -NoTypeInformation
...that produces this CSV...
"ID","Value 1","Value 2","Value 3","Value 4"
"1234","London","Serial1","HP","Laptop User"
"1234","London","Serial9","","Finance Application"
"2345","Moscow","Serial7","","HR Application"
This could be simplified by using a Where-Object filter and then exporting with Export-CSV.
$Search = Import-CSV "SEARCH.csv"
Import-CSV "DATA.csv" | Where-Object {$Search.ID -Contains $_.ID} | Export-CSV C:\exampleexportpath.csv -NoTypeInformation

Dictionary with multiple values per key

I need a datatype for the PowerShell to add different values to a key. which type should I choose?
The data are like:
+--------+--------+--------+--------+
| Serv1 | Serv2 | Serv3 | Serv4 |
| -------+--------+--------+------- |
| User1 | User2 | User3 | User4 |
| User3 | User1 | User2 | User4 |
| User7 | User8 | User9 | ------ |
+--------+--------+--------+--------+
This would be easier to answer with more information. But based on that, it looks like a [hashtable] where the values are arrays is what you want.
Example:
$hash = #{
Serv1 = #(
'User1',
'User3',
'User7'
)
Serv2 = #(
'User2',
'User1'
)
}
# add a new user to an existing key
$hash.Serv2 += 'User8'
# add a new key
$hash.Serv3 = #(
'User3',
'User2'
)