append rows in CSV using powershell - powershell
I have piece of code like below.
$month = Get-Date -Format "yyyy_MM"
$csv_location = "C:\brivo\csv\" + $month + ".csv"
if (!(Test-Path $csv_location))
{
$newcsv = {} | Select "Time","Name","Surname","Email","Telephone","Company","Department","Address","Postcode","City","State","Country" | Export-Csv $csv_location -NoTypeInformation
}
ForEach($line in $lines){
Try
{
$line = $line.Trim()
$file = "C:\brivo\json\" + $line
$data = Get-Content $file | ConvertFrom-Json
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm"
$userline = $timestamp,$data.name,$data.surname,$data.email,$data.telephone,$data.company_name,$data.department,$data.address,$data.postcode,$data.city,$data.state,$data.country
$userline | out-file $csv_location -Append
}
Catch [Exception]
{
Write-Host $_.Exception | format-list -force
}
}
where first part is creating csv file if not exist with headers. and in second part $lines is file names like 123.json, 456.json... and all those json files has content like below.
{
"name": "kamal",
"sur_name": "wewetwe",
"email": "asdasd#gmail.com",
"telephone": "311234544567",
"company_name": "",
"department": "",
"address": "qwe",
"postcode": "1234 ad",
"city": "qwe",
"state": "GR",
"country": "NL"
}
what I want is to append all this json data to csv file. I have tried like above but it adds data in 1st column itself.
Don't try to initialize your CSV file without data using Export-Csv, because that won't work:
The dummy object with $null property values created by Select-Object results not in just in a header row, but invariably also in a data row representing the $null values, which end up as empty strings; in other words, you'll get a data row that looks like this: ,,,,,,,,,,,
Instead, make sure that the properties of the objects representing data rows you pass to Export-Csv are named for the desired CSV columns.
Don't try to construct your data rows as an array of values to save to the CSV file as plain text;[1] instead, construct a [pscustomobject] for each data row, which Export-Csv automatically converts to a data row, with the property names serving as column names (as you attempted with your header row).
As the code below shows, you can construct an object in each iteration and pipe it to a single Export-Csv call for efficiency.
Use the -Encoding parameter to control the output character encoding as needed; notably, in Windows PowerShell the default encoding is ASCII(!).
-Append is still used in that single call, because it looks like you want to append to a preexisting target file, if present.
Important:
The first object sent to Export-Csv locks in the list of columns and their names based on its properties; subsequent objects are expected to have the same set of properties (or a meaningful subset).
Similarly, when appending to a preexisting CSV file with -Append, the objects to append must match the existing columns, although you can override this requirement with -Force.
$month = Get-Date -Format "yyyy_MM"
$csv_location = "C:\brivo\csv\" + $month + ".csv"
# $lines is assumed to be an array of your input JSON file names.
$lines | ForEach-Object {
Try
{
$file = "C:\brivo\json\" + $_.Trim()
$data = Get-Content -ErrorAction Stop -Raw $file |
ConvertFrom-Json -ErrorAction Stop
# Construct and output an object with the desired values and the
# properties named for the desired CSV columns.
[pscustomobject] #{
Time = Get-Date -Format 'yyyy-MM-dd HH:mm'
Name = $data.name
Surname = $data.surname
Email = $data.email
Telephone = $data.telephone
Company = $data.company_name
Department = $data.department
Address = $data.address
Postcode = $data.postcode
City = $data.city
State = $data.state
Country = $data.country
}
}
Catch
{
Write-Host $_.Exception | format-list -force
}
} | Export-Csv -NoTypeInformation -Append $csv_location
[1] If you send an array of values to Out-File, each value becomes its own line in the output file. While you could address that with $userline -join ',', such plain-text processing is brittle, because values with embedded , chars. wouldn't be handled correctly.
**this is some example **
#add your json files on temp dir
$a= Get-ChildItem C:\Temp\PatchingServer*
foreach($g in $a){
$j=gc $g
$f=$j| ConvertFrom-Json
$obj=New-Object PSobject
$obj | Add-Member Noteproperty "Port" $f.Port
$obj | Add-Member Noteproperty "ApplicationName" $f.ApplicationName
$obj | Add-Member Noteproperty "MaintenanceWindow" $f.MaintenanceWindow
$obj | Add-Member Noteproperty "BusinessUnit" $f.BusinessUnit
$obj | Add-Member Noteproperty "AppOwner" $f.AppOwner
$obj | Add-Member Noteproperty "AppID" $f.AppID
$obj | Add-Member Noteproperty "Location" $f.Location
$obj | export-csv C:\Temp\Patching.csv -NoTypeInformation -Append
}
Related
Trying to extract specific text and merge output with existing output
I want to extract text from a .txt file. The way the file is layed out is in this format (below first block). Optimally, I would like for the powershell script to take the content of username and votecount and output them side by side. With an integer of 25>= add the letter D beside it. With the output adding itself to a pre-existing output file. Say this week is week 1. And testuser voted 25 times. They should have the output "testuser" 25D. But say in week 2 they voted 24 times. Then it should be "testuser" 49D. However say they had 25 again. Output should then be "testuser" 50DD or 50D2?.. I have what I think should work as an initial baseline for the script which in itself doesn't work.. But combining an output with a pre existing output is beyond my capability. This needs to parse an entire txt file of some 100+ people. So imagine there's like an extra 100 users.. { "username": "testuser", "votecount": "42", "votesclaimed": "0", "lastvotetime": "2022-11-04 09:08:29", "steamid": "00000000000000000000" } Below is what I am working with. Get-Content -Raw C:\Users\--------\Desktop\votes.txt | ConvertFrom-txt | ForEach-Object { [pscustomobject] #{ UserName = $_.username VoteCount = '{0}{1}' -f $_.votecount, ('', 'D')[[int] $_.votecount -gt 25] } } | Export-Csv -NoTypeInformation -Encoding utf8 C:\Users\---------\Desktop\outvotes.csv
Try following : $match = Select-String -Path "c:\temp\test.txt" -Pattern '^\s*"(?<key>[^"]+)"\s*:\s*"(?<value>[^"]+)' $table = [System.Collections.ArrayList]::new() foreach( $row in $match.Matches ) { $key = $row.Groups["key"].Value $value = $row.Groups["value"].Value if($key -eq "username") { $newRow = New-Object -TypeName psobject $table.Add($newRow) | Out-Null } $newRow | Add-Member -NotePropertyName $key -NotePropertyValue $value } $table | Format-Table $groups = $table | Group-Object {$_.username}
PowerShell ForEach-Object Only Matching first Value passed
I am writing a PowerShell script that reads a CSV file in the following format: A,B,ProgrammeSeller,D,E,F,G,H,I,J,K,L,M,N,O,P,SellerCode,Date,Seller,Currency1,InvoiceAmmount,DiscountAmount,PurchasePrice,TotalsCurrency,TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice,Reviewed,AC,Signed ,,PROGRAMME,,,,,,,,,,,,,,380,09/12/2021,SELLER_BE,EUR,2813828.46,17594.22,2796234.24,EUR,0,0,0,Reviewed by:,,Signed: ,,PROGRAMME,,,,,,,,,,,,,,383,09/12/2021,SELLER_DE,EUR,3287812.58,17595.8,3270216.78,EUR,0,0,0,Reviewed by:,,Signed: ,,PROGRAMME,,,,,,,,,,,,,,383,09/12/2021,SELLER_DE,USD,1520725.4,11428.98,1509296.42,USD,0,0,0,Reviewed by:,,Signed: ,,PROGRAMME,,,,,,,,,,,,,,381,09/12/2021,SELLER_DK,DKK,6047281.25,26163.13,6021118.12,DKK,0,0,0,Reviewed by:,,Signed: ,,PROGRAMME,,,,,,,,,,,,,,381,09/12/2021,SELLER_DK,EUR,11376479.39,39580.28,11336899.11,EUR,0,0,0,Reviewed by:,,Signed: ,,PROGRAMME,,,,,,,,,,,,,,381,09/12/2021,SELLER_DK,USD,12571895.13,71198.51,12500696.62,USD,0,0,0,Reviewed by:,,Signed: And I want to group and sum 3 of the columns (InvoiceAmmount,DiscountAmount,PurchasePrice), and update the columns (TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice) on a new file which is a copy of the original but with the columns (TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice) updated with the values of the sums whenever there is a match on Currency value. I have written the following script: $csvFile = Import-Csv "C:\OutputFiles\OTC_Purchase_Report_EUProgramme_20220420_TMP.csv" $csvFinal = "C:\OutputFiles\OTC_Purchase_Report_EUProgramme_20220420.csv" function WriteTotalsToCSV { $totals | ForEach-Object { $totalCurrency = $_.Currency $totalInvoiceAmmount = $_.TotalInvoiceAmmount $totalDiscountAmmount = $_.TotalDiscountAmmount $totalPurchasePrice = $_.TotalPurchasePrice $csvFile | ForEach-Object { if($_.Currency1 -eq $totalCurrency){ $_.TotalsCurrency = $totalCurrency $_.TotalInvoiceAmmount = $totalInvoiceAmmount $_.TotalDiscountAmmount = $totalDiscountAmmount $_.TotalPurchasePrice = $totalPurchasePrice } $_ | Export-Csv $csvFinal -NoTypeInformation -Append } } } function InvoiceAmmountTotals { param ($file) $headers = ($file | Get-Member -MemberType NoteProperty).Name $totals = $file | Select-Object $headers | Group-Object Currency1 foreach ($total in $totals) { $showtotal = New-Object System.Management.Automation.PsObject $showtotal | Add-Member NoteProperty Currency $total.Name $showtotal | Add-Member NoteProperty TotalInvoiceAmmount ($total.Group | Measure-Object -Sum InvoiceAmmount).Sum $showtotal | Add-Member NoteProperty TotalDiscountAmmount ($total.Group | Measure-Object -Sum DiscountAmount).Sum $showtotal | Add-Member NoteProperty TotalPurchasePrice ($total.Group | Measure-Object -Sum PurchasePrice).Sum $showtotal } } #execution $totals = InvoiceAmmountTotals $csvFile WriteTotalsToCSV The script is grouping and summing the totals as expected but when it writes the new CSV file it is supposed to update the columns based on a match of column Currency1, but it is only doing this for the first match (in this case EUR) and ignoring the remaining matches i.e.: "A","B","ProgrammeSeller","D","E","F","G","H","I","J","K","L","M","N","O","P","SellerCode","Date","Seller","Currency1","InvoiceAmmount","DiscountAmount","PurchasePrice","TotalsCurrency","TotalInvoiceAmmount","TotalDiscountAmmount","TotalPurchasePrice","Reviewed","AC","Signed" "","","PROGRAMME","","","","","","","","","","","","","","380","09/12/2021","SELLER_BE","EUR","2813828.46","17594.22","2796234.24","EUR","153995434.65","797318.07","153198116.58","Reviewed by:","","Signed:" "","","PROGRAMME","","","","","","","","","","","","","","383","09/12/2021","SELLER_DE","EUR","3287812.58","17595.8","3270216.78","EUR","153995434.65","797318.07","153198116.58","Reviewed by:","","Signed:" "","","PROGRAMME","","","","","","","","","","","","","","383","09/12/2021","SELLER_DE","USD","1520725.4","11428.98","1509296.42","USD","0","0","0","Reviewed by:","","Signed:" "","","PROGRAMME","","","","","","","","","","","","","","381","09/12/2021","SELLER_DK","DKK","6047281.25","26163.13","6021118.12","DKK","0","0","0","Reviewed by:","","Signed:" "","","PROGRAMME","","","","","","","","","","","","","","381","09/12/2021","SELLER_DK","EUR","11376479.39","39580.28","11336899.11","EUR","153995434.65","797318.07","153198116.58","Reviewed by:","","Signed:" "","","PROGRAMME","","","","","","","","","","","","","","381","09/12/2021","SELLER_DK","USD","12571895.13","71198.51","12500696.62","USD","0","0","0","Reviewed by:","","Signed:" Note when column Currency1 value is USD the columns (TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice) are not being updated. Any suggestions on where I am going wrong here? Note: The CSV files are much larger, I have added a small number of entries for example purpose Thanks
It looks like you are looping through each line of the csv as many times as you have currencies because you are looping through $totals and inside of each of those loops you are looping through each line of the csv causing duplicates. Loop only once through the csv lines and for each line find the matching currency in your $totals array to update each csv line with. Finally output all at once to Export-Csv function WriteTotalsToCSV { # only one loop through csv lines $csvFile | ForEach-Object { $line = $_ # find matching currency in your $totals array using Where() $totalsMatch = $totals.Where({ $line.Currency1 -eq $_.Currency }) $line.TotalsCurrency = $totalsMatch.Currency $line.TotalInvoiceAmmount = $totalsMatch.TotalInvoiceAmmount $line.TotalDiscountAmmount = $totalsMatch.TotalDiscountAmmount $line.TotalPurchasePrice = $totalsMatch.TotalPurchasePrice $line # collect all the lines first and then export to csv once at the end } | Export-Csv -Path $csvFinal -NoTypeInformation }
import-csv, add values, export-csv results in file (results in length?)
The goal is for me to be able to take the values from csv1 and update them to csv2 where there is a match found based on a column value of id. I can see with the output the values are updating, but when I try to export it to a csv file I get the following... "Length" "60" "60" "60" "60" Here is the code. $inputCsv = Import-CSV './get_values.csv' -delimiter "," $updateCsv = Import-CSV './set_values.csv' -delimiter "," $output = $updateCsv | ForEach-Object { # Matching value $id = $_.id # Row of values found in 2nd file matching value from first file $rowFound = $inputCsv|Where-object "ID" -EQ $id # # Columns to update values if($rowFound -ne $Null -and $rowFound -ne ''){ $_.email = $rowFound.Email Write-Output $_.email $_.firstname = $rowFound.FirstName Write-Output $_.firstname $_.lastname = $rowFound.LastName Write-Output $_.lastname Write-Output "------------------------------------------------------------" } } $output | Export-Csv 'C:\scripts\powershell\output.csv' -NoTypeInformation I've tried the solutions from here: Export-CSV exports length but not name But I wasn't able to get any of them to work.
This is because you export raw string values, rather than structured objects - Export-Csv tries to discover the properties of the input objects, and strings only have one property - the length. Change the loop body to modify $_, and then output $_ at the very end - don't try to output anything else in between: $output = $updateCsv | ForEach-Object { # Matching value $id = $_.id # Row of values found in 2nd file matching value from first file $rowFound = $inputCsv | Where-object "ID" -EQ $id # # Columns to update values if($rowFound){ $_.email = $rowFound.Email $_.firstname = $rowFound.FirstName $_.lastname = $rowFound.LastName } # output the (perhaps modified) object, nothing else $_ }
Powershell - Insert column in between specific columns in csv file
I have 2 csv files First file: firstName,secondName 1234,Value1 2345,Value1 3456,Value1 4567,Value3 7645,Value3 Second file: firstName,fileSplitter,Csv2ColumnOne,Csv2ColumnTwo,Csv2ColumnThree 1234,,1234,abc,Value1 1234,,1234,asd,Value1 3456,,3456,qwe,Value1 4567,,4567,mnb,Value1 I want to insert column secondName in the second file in between columns firstName and fileSplitter. The result should look like this: firstName,secondName,fileSplitter,Csv2ColumnOne,Csv2ColumnTwo,Csv2ColumnThree 1234,Value1,,1234,abc,Value1 1234,Value1,,1234,asd,Value1 3456,Value1,,3456,qwe,Value1 4567,Value3,,4567,mnb,Value1 I'm trying the following code: Function InsertColumnInBetweenColumns { Param ($FirstFileFirstColumnTitle, $firstFile, [string]$1stColumnName, [string]$2ndColumnName, [string]$columnMergedFileBeforeInput) Write-Host "Creating hash table with columns values `"$1stColumnName`" `"$2ndColumnName`" From $OimFileWithMatches" $hashFirstFileTwoColumns = #{} Import-Csv $firstFile | ForEach-Object {$hashFirstFileTwoColumns[$_.$1stColumnName] = $_.$2ndColumnName} Write-Host "Complete." Write-Host "Appending Merge file with column `"$2ndColumnName`" from file $secondCsvFileWithLocalPath" Import-Csv $outputCsvFileWithLocalPath | Select-Object $columnMergedFileBeforeInput, #{n=$2ndColumnName; e={ if ($hashFirstFileTwoColumns.ContainsKey($_.$FirstFileFirstColumnTitle)) { $hashFirstFileTwoColumns[$_.$FirstFileFirstColumnTitle] } Else { 'Not Found' }}}, * | Export-Csv "$outputCsvFileWithLocalPath-temp" -NoType -Force Move-Item "$outputCsvFileWithLocalPath-temp" $outputCsvFileWithLocalPath -Force Write-Host "Complete." Write-Host "" } This function will be called in a for loop for each column found in the first file (can contain an indefinite number). For testing, I am only using 2 columns from the first file. I'm getting an error output resulting the following: Select : Property cannot be processed because property "firstName" already exists. At C:\Scripts\Tests\Compare2CsvFilesOutput1WithMatchesOnly.ps1:490 char:43 + Import-Csv $outputCsvFileWithLocalPath | Select $columnMergedFileBeforeInput, # ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (#{firstName=L...ntName=asdfas}:PSObject) [Select-Object], PSArgume ntException + FullyQualifiedErrorId : AlreadyExistingUserSpecifiedPropertyNoExpand,Microsoft.PowerShell.Commands.SelectObjectC ommand I know the issue is where it says Select-Object $columnMergedFileBeforeInput,. How can I get the loop statement to insert the column in between the before column (name is specified), and append the rest using *? Update Just an fyi, changing this line Select-Object $columnMergedFileBeforeInput, #{n=$2ndColumnName..... to this line Select-Object #{n=$2ndColumnName..... works, it just attaches the columns out of order. That is why I'm trying to insert the column in between. Maybe if i do it this way but insert the columns in backwards using the for loop, this would work...
Not sure if this is the most efficient way to do it, but it should do the trick. It just adds the property to the record from file2, then reorders the output so secondName is the second column. You can output results to csv where required too (ConvertTo-Csv). $file1 = Import-Csv -Path file1.csv $file2 = Import-Csv -Path file2.csv $results = #() ForEach ($record In $file2) { Add-Member -InputObject $record -MemberType NoteProperty -Name secondName -Value $($file1 | ? { $_.firstName -eq $record.firstName } | Select -ExpandProperty secondName) $results += $record } $results | Select-Object -Property firstName,secondName,fileSplitter,Csv2ColumnOne,Csv2ColumnTwo,Csv2ColumnThree
I've created the following function. What it does is find the match (in this case "firstname") and adds the matching columnname to the new array afther the columnname on which the match is made (little difficult to explain in my poor English). function Add-ColumnAfterMatchingColumn{ [CmdletBinding()] param( [string]$MainFile, [string]$MatchingFile, [string]$MatchColumnName, [string]$MatchingColumnName ) # Import data from two files $file1 = Import-Csv -Path $MainFile $file2 = Import-Csv -Path $MatchingFile # Find column names and order them $columnnames = $file2 | gm | where {$_.MemberType -like "NoteProperty"} | Select Name | %{$_.Name} [array]::Reverse($columnnames) # Find $MatchColumnName index and put the $MatchingColumnName after it $MatchColumnNameIndex = [array]::IndexOf($columnnames, $MatchColumnName) if($MatchColumnNameIndex -eq -1){ $MatchColumnNameIndex = 0 } $columnnames = $columnnames[0..$MatchColumnNameIndex] + $MatchingColumnName + $columnnames[($MatchColumnNameIndex+1)..($columnnames.Length -1)] $returnObject = #() foreach ($item in $file2){ # Find corresponding value MatchingColumnName in $file1 and add it to the current item $item | Add-Member -Name "$MatchingColumnName" -Value ($file1 | ?{$_."$($MatchColumnName)" -eq $item."$($MatchColumnName)"})."$MatchingColumnName" -MemberType NoteProperty # Add current item to the returnObject array, in the correct order $newItem = New-Object psobject foreach ($columnname in [string[]]$columnnames){ $newItem | Add-Member -Name $columnname -Value $item."$columnname" -MemberType NoteProperty } $returnObject += $newItem } return $returnObject } When you run this function you will have the following output: Add-ColumnAfterMatchingColumn -MainFile C:\Temp\file1.csv -MatchingFile C:\Temp\file2.csv -MatchColumnName "firstname" -MatchingColumnName "secondname" | ft firstName secondname fileSplitter Csv2ColumnTwo Csv2ColumnThree Csv2ColumnOne --------- ---------- ------------ ------------- --------------- ------------- 1234 Value1 abc Value1 1234 1234 Value1 asd Value1 1234 3456 Value1 qwe Value1 3456 4567 Value3 mnb Value1 4567
Get a variable by dynamic variable name
How does one access data imported from a CSV file by using dynamic note property names? That is, one doesn't know the colunm names beforehand. They do match a pattern and are extracted from the CSV file when the script runs. As for an example, consider a CSV file: "Header 1","Header A","Header 3","Header B" 0,0,0,0 1,2,3,4 5,6,7,8 I'd like to extract only columns that end with a letter. To do this, I read the header row and extract names with a regex like so, $reader = new-object IO.StreamReader("C:\tmp\data.csv") $line = $reader.ReadLine() $headers = #() $line.Split(",") | % { $m = [regex]::match($_, '("Header [A-Z]")') if($m.Success) { $headers += $m.value } } This will get all the column names I care about: "Header A" "Header B" Now, to access a CSV file I import it like so, $csvData = import-csv "C:\tmp\data.csv" Import-CSV will create a custom object that has properties as per the header row. One can access the fields by NoteProperty names like so, $csvData | % { $_."Header A" } # Works fine This obviously requires one to know the column name in advance. I'd like to use colunn names I extracted and stored into the $headers. How would I do that? Some things I've tried so far $csvData | % { $_.$headers[0] } # Error: Cannot index into a null array. $csvData | % { $np = $headers[0]; $_.$np } # Doesn't print anything. $csvData | % { $_.$($headers[0]) } # Doesn't print anything. I could change the script like so it will write another a script that does know the column names. Is that my only solution?
I think you want this: [string[]]$headers = $csvdata | gm -MemberType "noteproperty" | ?{ $_.Name -match "Header [a-zA-Z]$"} | select -expand Name $csvdata | select $headers Choose the headers that match the condition (in this case, ones ending with characters) and then get the csv data for those headers.
the first thing ( and the only one... sorry) that came in my mind is: $csvData | % { $_.$(( $csvData | gm | ? { $_.membertype -eq "noteproperty"} )[0].name) } for get the first's column values and $csvData | % { $_.$(( $csvData | gm | ? { $_.membertype -eq "noteproperty"} )[1].name) } for second column and so on.... is this what you need?
you can use custom script to parse csv manually: $content = Get-Content "C:\tmp\data.csv" $header = $content | Select -first 1 $columns = $header.Split(",") $indexes = #() for($i; $i -lt $columns.Count;$i++) { # to verify whether string end with letter matches this regex: "[A-Za-z]$" if ($column[$i] -match "[A-Za-z]$") { $indexes += $i } } $outputFile = "C:\tmp\outdata.csv" Remove-Item $outputFile -ea 0 foreach ($line in $content) { $output = "" $rowcol = $line.Split(",") [string]::Join(",", ($indexes | foreach { $rowcol[$_] })) | Add-Content $outputFile }