1

I have this first CSV:

Server,Info  
server1,item1
server1,item1

and this 2nd CSV:

Server,Info  
server2,item2
server2,item2

And I am trying to get this output:

Server,Server,Info,Info
server1,server2,item1,item2
server1,server2,item1,item2

As you see, the problem is that the headers of the 2 CSV have the same names, which cause a problem if I parse them into objects and loop over keys.

So I am trying to merge them then reordering them as strings, but my mind can't figure how to do it in the last for loop:

$file1 = Get-Content ".\Powershell test\A.csv"
$file2 = Get-Content ".\Powershell test\B.csv"

$content = for ($i = 0; $i -lt $file1.Length; $i++) {
    '{0},{1}' -f $file1[$i].Trim(), $file2[$i].Trim()
}

$content | Out-File  ".\Powershell test\merged.csv"

$firstFileParsed = Import-Csv -Path ".\Powershell test\B.csv"
$secondFileParsed = Import-Csv -Path ".\Powershell test\B.csv"

$secondFilePath =  ".\Powershell test\B.csv"
$contentOf2ndFile = Get-Content $secondFilePath

$csvColumnNames = (Get-Content '.\Powershell test\B.csv' |
                  Select-Object -First 1).Split(",")

$newColumns = @()

foreach($header in $csvColumnNames) {
    $newColumns += $header
}

$newColumns = $newColumns -join ","
$contentOf2ndFile[0] = $newColumns
$contentOf2ndFile | Out-File ".\Powershell test\temp.csv"

$tempObject = Import-Csv -Path ".\Powershell test\temp.csv"
$tempFile = Get-Content ".\Powershell test\temp.csv"

$array = @()
$tempArr = @()

for ($i = 0; $i -lt $file1.Length; $i++) {
    $tempArr1 = $file1[$i] -split ","
    $tempArr2 = $tempFile[$i] -split ","

    for ($j = 0; $j -lt $tempArr1.Length; $j++) {
        $tempArr += $tempArr1[$j] +  "," + $tempArr2[$j]
        $tempArr
    }

    $array += $tempArr
}

$array | Out-File '.\Powershell test\merged.csv'
iRon
  • 20,463
  • 10
  • 53
  • 79
  • 1
    Please take a step back and describe the actual problem you're trying to solve instead of what you perceive as the solution. Why do you need two columns with identical header name? – Ansgar Wiechers Feb 26 '19 at 10:22
  • 1
    I'm with @AnsgarWiechers on this one -- having duplicate header names isn't really useful as you can't refer to them and know what you'll get. Surely headers like `Server1,Info1,Server2,Info2` would be more useful? – henrycarteruk Feb 26 '19 at 10:44
  • A `CSV` file with duplicate headers is simply not a valide `CSV` format. If you `Import-Csv` such a `CSV` file, you will get a **Import-Csv : The member "Server" is already present.** – iRon Feb 26 '19 at 10:47
  • You might use the default output of this [`Join-Object`](https://www.powershellgallery.com/packages/Join) cmdlet: `$File1 | Join $File2` where the columns are merged in an array: `{server1, server2} {item1, item2}`. Than you can access your result (`$Result = $File1 | Join $File2`) like: `$Result[0].Server[1]` ` – iRon Feb 26 '19 at 10:52
  • 1
    @iRon To my knowledge the [CSV format specification](https://tools.ietf.org/html/rfc4180) doesn't forbid duplicate column titles, so technically the format *is* valid CSV. It's just not very useful in about every practical application, and particularly not in PowerShell, where the records are represented as the properties of custom objects (whose names must be unique). – Ansgar Wiechers Feb 26 '19 at 11:00
  • I am open to all suggestions....I don't mind Server1,Info1,Server2,Info2 –  Feb 26 '19 at 12:02
  • @James C. kindly check my comment on the first answer –  Feb 26 '19 at 12:05
  • @Ansgar Wiechers that's because the ultimate goal is for people to work on it on excel, so they want these similar headers visually side by side "Server, Server, Info, Info", I don't mind adding numbers to headers to avoid bugs tho. –  Feb 26 '19 at 12:11
  • Does this answer your question? [In PowerShell, what's the best way to join two tables into one?](https://stackoverflow.com/questions/1848821/in-powershell-whats-the-best-way-to-join-two-tables-into-one) – TylerH May 23 '22 at 13:15

2 Answers2

2

What you suggest is not very useful or even valid CSV. IMHO only two results would make sense:

This:

Server1,Info1,Server2,Info2
server1,item1,server2,item2
server1,item1,server2,item2

Or this:

Server,Info
server1,item1
server1,item1
server2,item2
server2,item2

First approach:

$csv1 = Import-Csv ".\Powershell test\A.csv"
$csv2 = Import-Csv ".\Powershell test\B.csv"

$merged = for($i = 0; $i -lt $csv1.Count; $i++) {
    $new = new-object psobject
    $entry1 = $csv1[$i]
    $entry1 | Get-Member -Type NoteProperty | foreach {
        Add-Member -InputObject $new -MemberType NoteProperty -Name ($_.Name + "1") -Value $entry1.($_.Name)
    }
    $entry2 = $csv2[$i]
    $entry2 | Get-Member -Type NoteProperty | foreach {
        Add-Member -InputObject $new -MemberType NoteProperty -Name ($_.Name + "2") -Value $entry2.($_.Name)
    }
    $new
}

$merged | Export-Csv ".\Powershell test\merged.csv"

Second approach:

$csv1 = Import-Csv ".\Powershell test\A.csv"
$csv2 = Import-Csv ".\Powershell test\B.csv"

$merged = $csv1 + $csv2

$merged | Export-Csv ".\Powershell test\merged.csv"

UPDATE

If you want exactly your output (and the files are certain to have the same headers and line count), you could use unique headers first, and then simply rename them later:

$csv1 = Import-Csv ".\Powershell test\A.csv"
$csv2 = Import-Csv ".\Powershell test\B.csv"
$merged = for($i = 0; $i -lt $csv1.Count; $i++) {
    $new = New-Object PSObject
    ("Server", "Info") | foreach {
        Add-Member -InputObject $new -MemberType NoteProperty -Name ($_ + "1") -Value $csv1[$i].$_
        Add-Member -InputObject $new -MemberType NoteProperty -Name ($_ + "2") -Value $csv2[$i].$_
    }
    $new
}
$header = $true
$merged | ConvertTo-Csv -NoTypeInformation | foreach {
    if ($header) {
        $header = $false
        # remove the numbers from the headers
        $_ -replace "\d", ""
    }
    else { $_ }
} | Out-File ".\Powershell test\merged.csv"

Explanations:

Count is available in Powershell for all collections, and safer than Length which is a property of arrays only. But in this case, both should work.

In the loop, a new empty object is created (with New-Object) and then populated by adding the members of the parsed CSV objects (with Add-Member). A counter is added to the property names to make them unique.

The collection of these objects ($merged) is then converted to CSV, the numbers in the header line removed, and everything saved to file.

marsze
  • 15,079
  • 5
  • 45
  • 61
  • That's because business people want to convert it to excel, and they want these headers side by side, like Server1,Server2...etc so they can work on the statistics, merging them like Server1,Info1,Server2,Info2 won't be useful for them. I don't mind modification of headers but they want the output like in the OP –  Feb 26 '19 at 12:03
  • that's awesome, I would really love to see an explanation! So from my understanding, you created a new object on every iteration , and you added a member of each file on every iteration to this new obj? But what is the 'container' that you added each iteration's result to (aka the new object created at each iteration)? I am not seeing an object initiated before the for loop O.o –  Feb 26 '19 at 13:19
  • And why .count instead of .length in the for loop? –  Feb 26 '19 at 13:21
  • Hmmm I am not sure if I did something wrong, I've tried your script but I got `"Server","Server","Info","Info" /new line "server1","server2",, /new line "server1","server2",, ` /new line. Info's values are missing –  Feb 26 '19 at 13:24
  • @Sam I had the same issue. You have extra spaces in your CSV's so basically the header is not "Info" but "Info ". I leave it up to you what you want to do about that. – marsze Feb 26 '19 at 13:32
  • @Sam And, I've added some explanations in my post. Let me know if you have any further questions. – marsze Feb 26 '19 at 13:40
0

As it appears that there several used cases to discern unrelated property keys instead of merging them, I have added a new feature. The -Unify (formally/alias -Mergeparameter) to the Join-Object cmdlet, now accepts a one or two dynamic keys to distinguish unrelated column pairs in a join.

The -Unify (alias-Merge) parameter defines how to unify the left and right object with respect to the unrelated common properties. The common properties can discerned (<String>[,<String>]) or merged (<ScriptBlock>). By default the unrelated common properties wil be merged using the expression: {$LeftOrVoid.$_, $RightOrVoid.$_}

<String>[,<String>] If the value is not a ScriptBlock, it is presumed a string array with one or two items defining the left and right key format. If the item includes an asterisks (*), the asterisks will be replaced with the property name otherwise the item will be used to prefix the property name.

Note: A consecutive number will be automatically added to a common property name if is already used.

...

Example:

$Csv1 = ConvertFrom-Csv 'Server,Info
server1,item1
server1,item1'

$Csv2 = ConvertFrom-Csv 'Server,Info
server2,item2
server2,item2'

$Csv1 | Join $Csv2 -Unify *1, *2

Result:

Server1 Server2 Info1 Info2
------- ------- ----- -----
server1 server2 item1 item2
server1 server2 item1 item2
iRon
  • 20,463
  • 10
  • 53
  • 79
  • Hi iRon, I haven't seen -Unify in any powershell documentation, how can we get this feature? Is it a custom build you made? And in which PS version the Join cmdlet can be found? –  Mar 05 '19 at 11:07
  • Sorry, it was only in my comment to the question, I forgot to add it to my answer (it is now). Anyway, the [`Join-Object`](https://www.powershellgallery.com/packages/Join) is indeed a custom cmdlet which can be downloaded from the PowerShell Gallery. – iRon Mar 05 '19 at 11:16
  • Interesting, I didn't know Powershell has its own package manager/custom libraries :) –  Mar 05 '19 at 11:21
  • I installed the package, I did get an error 'Join : The term 'Join' is not recognized as the name of a cmdlet' –  Mar 05 '19 at 11:25
  • Sorry, I missed your last comment, but you can invoke the script by dot sourcing, e.g.: `. .\Join.ps1`, see also: https://stackoverflow.com/a/45483110/1701026 – iRon Apr 07 '19 at 19:36