Powershell - add multiple columns to multiple excel files - powershell

I have a folder that has over 50 excel files in it ("Project dump' in the path below.) All of these files contain the same exact data (its archived monthly data that's used for a MoM report) I need to update all of these files to add 10 new column headers - none of these columns will have any data in them, they just need to be added to the table to match the most current month extract that will have data in it going forward.
I've been using Powershell, and have a script that can add one column to one file at a time, but it would honestly be faster for me to manually open each file and add the columns myself. I cant seem to figure out how to change my script to do what its doing to multiple files (and with multiple columns), any help would be greatly appreciated!
background; the reference is a specific file in my project dump folder. Column 50 is the first blank column, that needs to be added to the table:
(Get-ChildItem "C:\Downloads\Project dump\ArchiveJAN21.xlsx")|
foreach-object {
$xl=New-Object -ComObject Excel.Application
$wb=$xl.workbooks.open($_)
$ws = $wb.worksheets.Item(1)
$ws.Columns.ListObject.ListColumns.Add(50)
$ws.Cells.Item(1,50) ='Call Type'
$wb.Save()
$xl.Quit()
while([System.Runtime.Interopservices.Marshal]::ReleaseComObject([System.__ComObject]$xl)){'released'| Out-Null}
}

You need to define the Excel object before the loop and quit afterwards.
Also, use Get-ChildItem to get FileInfo objects from a folder path, not a hardcoded path to a file.
Try:
# an array with the new column names
$newColumns = 'Call Type','NewCol2','NewCol3','NewCol4','NewCol5','NewCol6','NewCol7','NewCol8','NewCol9','NewCol10'
# create the Excel object outside of the loop
$xl = New-Object -ComObject Excel.Application
$xl.Visible = $false
# loop thtrough the files in the folder
(Get-ChildItem -Path 'C:\Downloads\Project dump' -Filter '*.xlsx' -File ) | ForEach-Object {
$wb = $xl.WorkBooks.Open($_.FullName)
$ws = $wb.Worksheets.Item(1)
# get the number of columns in the sheet
$startColumn = $ws.UsedRange.Columns.Count
for ($i = 0; $i -lt $newColumns.Count; $i++) {
$startColumn++ # increment the column counter
$ws.Cells.Item(1, $startColumn) = $newColumns[$i]
}
$wb.Close($true) # $true saves the changes
}
# quit Excel and clean COM objects from memory
$xl.Quit()
# clean up the COM objects used
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ws)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

Related

I have a script that makes some manipulation with .xlsx file. How do i loop it with all files within folder?

I have script that updates query in excel file
$filePath = "C:\Scripts\SheetToRefresh.xlsx"
$excelObj = New-Object -ComObject Excel.Application
$excelObj.Visible = $true
$workBook = $excelObj.Workbooks.Open($filePath)
$workSheet = $workBook.Sheets.Item("Data")
$workSheet.Select()
$workBook.RefreshAll()
$workBook.Save()
Original script comes from here
Now i need to loop it wihtin folder, i came up with:
$files = Get-ChildItem "C:\path" -Filter *.xlsx
foreach ($f in $files){
}
but struggling with changing filename for each file.(newbie with ps)
Let's break down what needs to happen:
Before:
Open Excel
Enumerate files
During, for each file:
Open workbook
Run the relevant part of your existing script
Save and close workbook
After:
Close Excel
So, let's start by moving the "Before" actions to the top of your new script:
# Open Excel
$excelObj = New-Object -ComObject Excel.Application
$excelObj.Visible = $true
# Enumerate files
$files = Get-ChildItem "C:\path" -Filter *.xlsx
Now we need to move the relevant parts of the existing script into the new loop. To get the full path of the file object returned by Get-ChildItem, use the FullName property:
foreach($file in $files){
# Open workbook from $file
$workBook = $excelObj.Workbooks.Open($file.FullName)
# Refresh query results
$workSheet = $workBook.Sheets.Item("Data")
$workSheet.Select()
$workBook.RefreshAll()
# Save updated workbook to file
$workBook.Save()
# Close workbook
$workBook.Close()
}
And finally we just need to quit Excel:
$excelObj.Quit()

Modify a .csv file in powershell automatically

I try to create a powershell script, to perform a few steps:
In a specific folder, I put a .xlsx file, it converts it to csv. Until now I got this:
$ErrorActionPreference = 'Stop'
Function Convert-CsvInBatch
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)][String]$Folder
)
$ExcelFiles = Get-ChildItem -Path $Folder -Filter *.xlsx -Recurse
$excelApp = New-Object -ComObject Excel.Application
$excelApp.DisplayAlerts = $false
$ExcelFiles | ForEach-Object {
$workbook = $excelApp.Workbooks.Open($_.FullName)
$csvFilePath = $_.FullName -replace "\.xlsx$", ".csv"
$workbook.SaveAs($csvFilePath, [Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSV)
$workbook.Close()
}
# Release Excel Com Object resource
$excelApp.Workbooks.Close()
$excelApp.Visible = $true
Start-Sleep 5
$excelApp.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excelApp) | Out-Null
}
#
# 0. Prepare the folder path which contains all excel files
$FolderPath = "C:\exacthpath"
Convert-CsvInBatch -Folder $FolderPath
The columns in the file, are still there, so I want to remove them, and insert a ';' instead, like:
H;1;43;185;
At this point I'm stuck. I can import it into Powershell like:
Import-Csv -Path 'C:\folder\filename.csv' | ForEach-Object {
$_
}
I get this look, and the most important task is here, in the first row only:
H;1;43;185;
This should be modified into:
H;01;43;185
the rest should be left untouched.
After I need to export back it into a CSV file, like:
Export-Csv -Path 'C:\folder\modified_filename.csv'
But this whole process should be inserted in one single powershell script, which performs the above steps on it's own. So in short:
identifies any .xlsx file - regardless of it's name
convers it into .csv
modifies the outlook of the document, to separate the columns with a ";"
modify the first line to have 'H;01;43;185' - this is a static line, it will always look like this
save the created file as a final .csv file
Can you help me somehow to include/optimize the above scripts and let powershell perform the modification too? Example content of a file like this (final look) Usually it includes more 1000+ lines:
H;01;43;185
D;111;3;1042;2
D;222;3;1055;3
D;333;3;1085;1
T;3;;;
Any help is highly appreciated.
Regards,
Armin
If as you say in your comment, your Excel already creates a csv with the semi-colon as delimiter, you can do this inside the loop, just below $workbook.Close()
# read the file created by Excel as string array
$data = Get-Content $csvFilePath
# overwrite the file with just the new header
Set-Content -Path $csvFilePath -Value 'H;01;43;185'
# add the rest of the data to the file
$data[1..($data.Count -1)] | Add-Content -Path $csvFilePath
P.S. I would delete the lines
$excelApp.Visible = $true
Start-Sleep 5
because I don't see the need to have Excel show itself and pause the function for 5 seconds.. Instead, have Excel not show at all so it will work a lot faster by adding
$excelApp.Visible = $false
right after you have created the $excelApp

Powershell script to convert excel files to csv files with card numbers with 16 digits

I have this excel sheets and I want to have the same format for csv files. Could some one help me with a automation script please (to convert multiple excel sheets to csv files)??
I tried this script, but the 16th digit of the card number is turning to be zero as excel can read only 15 digits right. Can we modify this code to convert multiple excel sheets to csv files?
Could someone help me with this.
Convert Excel file to CSV
$xlCSV=6
$Excelfilename = “C:\Temp\file.xlsx”
$CSVfilename = “C:\Temp\file.csv”
$Excel = New-Object -comobject Excel.Application
$Excel.Visible = $False
$Excel.displayalerts=$False
$Workbook = $Excel.Workbooks.Open($ExcelFileName)
$Workbook.SaveAs($CSVfilename,$xlCSV)
$Excel.Quit()
If(ps excel){kill -name excel}
Excel is really particular in its handling of CSV files..
Although the 16 digit numbers are written out in full when using the SaveAs method, if you re-open it by double-clicking the csv file, Excel screws up these numbers by converting them to numeric values instead of strings.
In order to force Excel to NOT interpret these values and simply regard them as strings, you need to adjust the values in the csv file afterwards, by prefixing them with a TAB character.
(this will make the file useless for other applications..)
Of course, you need to know the correct column header to do this.
Let's assume your Excel file looks like this:
As you can see, the value we need to adjust is stored in column Number
To output csv files on which you can double-click so they are opened in Excel, the code below would do that for you:
$xlCSV = 6
$Excelfiles = 'D:\test.xlsx', 'D:\test2.xlsx' # an array of files to convert
$ColumnName = 'Number' # example, you need to know the column name
# create an Excel COM object
$Excel = New-Object -comobject Excel.Application
$Excel.Visible = $False
$Excel.DisplayAlerts = $False
foreach ($fileName in $Excelfiles) {
$Workbook = $Excel.Workbooks.Open($fileName)
# use the same file name, but change the extension to .csv for output
$CSVfile = [System.IO.Path]::ChangeExtension($fileName, 'csv')
# have Excel save the csv file
$Workbook.SaveAs($CSVfile, $xlCSV)
$Workbook.Close($false)
}
# close excel and clean up the used COM objects
$Excel.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
# now import the csv files just created and update the card number
# column by prefixing the value with a TAB character ("`t").
# this will effectively force Excel NOT to interpret the value as numeric.
# you better not do this inside the same loop, because Excel keeps a lock
# on outputted csv files there.
foreach ($fileName in $Excelfiles) {
# use the same file name, but change the extension to .csv for output
$CSVfile = [System.IO.Path]::ChangeExtension($fileName, 'csv')
# the '-UseCulture' switch makes sure the same delimiter character is used
$csv = Import-Csv -Path $CSVfile -UseCulture
foreach ($item in $csv) { $item.$ColumnName = "`t" + $item.$ColumnName }
# re-save the csv file with updates values
$csv | Export-Csv -Path $CSVfile -UseCulture -NoTypeInformation
}

Powershell Mass Rename files with a excel reference list

I need help with PowerShell.
I will have to start renaming files in a weekly basis which I will be renaming more than 100 a week or more each with a dynamic name.
The files I want to rename are in a folder name Scans located in the "C: Documents\Scans". And they would be in order, to say time scanned.
I have an excel file located in "C: Documents\Mapping\ New File Name.xlsx.
The workbook has only one sheet and the new names would be in column A with x rows. Like mention above each cell will have different variables.
P Lease make comments on your suggestions so that I may understand what is going on since I'm a new to coding.
Thank you all for your time and help.
Although I agree with Ad Kasenally that it would be easier to use CSV files, here's something that may work for you.
$excelFile = 'C:\Documents\Mapping\New File Name.xlsx'
$scansFolder = 'C:\Documents\Scans'
########################################################
# step 1: get the new filenames from the first column in
# the Excel spreadsheet into an array '$newNames'
########################################################
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$workbook = $excel.Workbooks.Open($excelFile)
$worksheet = $workbook.Worksheets.Item(1)
$newNames = #()
$i = 1
while ($worksheet.Cells.Item($i, 1).Value() -ne $null) {
$newNames += $worksheet.Cells.Item($i, 1).Value()
$i++
}
$excel.Quit
# IMPORTANT: clean-up used Com objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($worksheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
########################################################
# step 2: rename the 'scan' files
########################################################
$maxItems = $newNames.Count
if ($maxItems) {
$i = 0
Get-ChildItem -Path $scansFolder -File -Filter 'scan*' | # get a list of FileInfo objects in the folder
Sort-Object { [int]($_.BaseName -replace '\D+', '') } | # sort by the numeric part of the filename
Select-Object -First ($maxItems) | # select no more that there are items in the $newNames array
ForEach-Object {
try {
Rename-Item -Path $_.FullName -NewName $newNames[$i] -ErrorAction Stop
Write-Host "File '$($_.Name)' renamed to '$($newNames[$i])'"
$i++
}
catch {
throw
}
}
}
else {
Write-Warning "Could not get any new filenames from the $excelFile file.."
}
You may want to have 2 columns in the excel file:
original file name
target file name
From there you can save the file as a csv.
Use Import-Csv to pull the data into Powershell and a ForEach loop to cycle through each row with a command like move $item.original $item.target.
There are abundant threads describing using import-csv with forEach.
Good luck.

Copy Sheets from Existing to new Workbook and use a cell as a reference for file name

Am trying to automate certain tasks that I have to do, that although simple are tedious, due to the number of files. I currently have a script that will refresh every file within a folder, now these files have more worksheets that what my client needs, so after refreshing, I need to copy/paste the first two sheets in a new workbook, save in a general location where the client pick's it up. I have added what I thought was good code to do this copy/paste, but unfortunately, I'm getting errors in the copy/paste section as well as the SaveAs part. I did some research here and at "powershell.org", but couldn't find anything that helped :(.
This is my code:
Measure-Command {
$excel = new-object -comobject excel.application
$excel.DisplayAlerts = $false
$excelFiles = Get-ChildItem -Path "Network folder location" -Include *.xls, *.xlsm,*.xlsx, *.lnk -Recurse
Foreach($file in $excelFiles) {
$workbook = $excel.workbooks.open($file.fullname)
foreach ($Conn in $workbook.Connections){
$Conn.OLEDBConnection.BackgroundQuery = $false
$Conn.refresh()
}
$workBook.RefreshAll()
$workbook.save()
$wb2 = $excel.Workbooks.Add()
$sheetToCopy = $workbook.sheets.item(1),$workbook.sheets.item(2) #Source
$sheetToCopy.CopyTo($wb2) #Destination
$filename = $wb2.Sheets.Item(2).Cells.Item(4,2) #Destination file, 2nd sheet, column D row 2 has what I want to call the file (RVP John Doe - Dashboard)
$wb2.SavesAs("Networkfolder\$filename.xlsx")
$workbook.close()
$wb2.close()
}
$excel.quit()
}