PowerShell: Format second sheet within xlsx file
I am working with an xlsx file that has two sheets within it.
I am uploading data into these sheets and formatting it.
I am able to successfully format the first sheet but not the second sheet.
This is the code for how I format the first sheet:
# Format Data: Autofit Columns
$lgTime = "[{0:HH:mm:ss}]" -f (Get-Date)
Write-Host "$lgTime Autofitting Data Columns..."
$range2autofit = $worksheet.UsedRange
$rowCount = $range2autofit.Rows.Count
[void] $range2autofit.EntireColumn.Autofit()
$lgTime = "[{0:HH:mm:ss}]" -f (Get-Date)
write-host "$lgTime Creating Excel Table Format ..."
$tableStyle = "TableStyleMedium9"
$tableStyle = "TableStyleLight21"
$Worksheet.Columns.Item("A").NumberFormat = "MM/DD/YYYY"
$ListObject = $WorkBook.ActiveSheet.ListObjects.Add(1, $range2autofit, $null , 1, $null, $tableStyle)
I would like the same formatting to be applied to the second sheet within the file but am having trouble doing that. I have tried using that same code again but with small changes such as:
Workbook.Worksheet.Item(2).UsedRange
Workbook.Worksheet.Item("Sheet2Name").UsedRange
My thought process is that I should be able to use the same code but just access the second sheet in it, I think I am just not accessing the second sheet correctly. That could be completely wrong though.
Edit:
This is where I defined $workbook and added a sheet to the xlsx file which is followed by the renaming of each sheet
$dataFile = "FILE LOCATION.xlsx"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$workbook = $objExcel.Workbooks.Open($dataFile)
$worksheet = $workbook.Worksheets.Add()
$worksheetOne = $workbook.Worksheets.Item(1)
$worksheetOne.Name = "Sheet1Name"
$worksheetTwo = $workbook.Worksheets.Item(2)
$worksheetTwo.Name = "Sheet2Name"
From the code you added in your edit, it looks like you're getting tripped up when adding a sheet, then using the wrong index later? Worksheets.Add() creates a blank worksheet at index 2 by default, not at the end. For example:
# A good way to check and see what you're doing with your sheets:
$workbook.Worksheets | select Index,Name
Index Name
----- ----
1 Sheet1
2 Sheet2
# add a new sheet
$worksheet = $workbook.Worksheets.Add()
# check again
$workbook.Worksheets | select Index,Name
Index Name
----- ----
1 Sheet1
2 Sheet3 # whoops!
3 Sheet2
To add before/after a specific worksheet instead, you can specify like this:
# missing value is required for COM functions
$newSheet = $workbook.Worksheets.add(
[System.Reflection.Missing]::Value, ## before index n
$workbook.Worksheets.Item(2) ## after index n
)
Then just be careful when you select your sheets, and you should be good to go!
# Setting sheet properties by referring to variable
$ws1 = $workbook.Worksheets.Item(1)
$ws1.Name = "Sheet1Name"
$ws1.UsedRange.EntireColumn.AutoFit()
$ws2 = $workbook.Worksheets.Item(2)
$ws2.Name = "Sheet2Name"
$ws2.UsedRange.EntireColumn.AutoFit()
Related
Good Morning,
I am new to Power Shell and I cobbled together a script to open, copy , and paste data from one excel sheet to another on a network drive. I can run it once and it works , but if I try to run it again I get the "Cannot access read-only document". I assume I am not opening and closing the excel application and workbooks properly. Any advice is appreciated.
#Start Excel
$excel = New-Object -ComObject Excel.Application
#$excel.Visible = $true
#$excel.DisplayAlerts = $false
$wb1= $excel.workbooks.open($file1)
$wb2 = $excel.workbooks.open($file2)
$ws1 = $wb1.WorkSheets.item(1)
$ws1exemptions = $wb1.WorkSheets.item(4)
$ws1totals = $wb1.WorkSheets.item(3)
$ws2 = $wb2.Worksheets.item(1)
$ws1.activate()
#Get number of rows to delete on Sheet1
$mydelrowcount = $ws1.UsedRange.rows.count
#Assign variable to make range A2:H(number of rows) $range = $mysheet.range("A2:H" + $myrowcount)
$mydelrange = "A2:H" + $mydelrowcount
#Select the rows
$deleteme = $ws1.range("A2:H" + $mydelrange)
#Delete rows fromm $file1!Sheet1
$deleteme.clear()
#Go to Sheet 1 of APOTHER (file2)
$ws2.activate()
#Get number of rows to copy from Sheet1
$mynewrowcount = $ws2.UsedRange.rows.count
#Assign variable to make range A2:H(number of rows)
$mynewrange = "A2:H" + $mynewrowcount
#Select the rows
$selectme = $ws2.range("A2:H" + $mynewrowcount)
#Copy the rows
$selectme.Copy()
#Go to Sheet1 of TestFiletoSend (file1)
$ws1.activate()
#Select the same number of rows in Sheet1 (variable in $mynewrange)
$ws1.Range($mynewrange).Select()
#Paste the rows into A2:H(number of rows)
$ws1.Paste()
$ws1.Cells.Range("A1").Select()
$ws1totals.activate()
$wb1.SaveAs($file1)#
$wb1.Close()
$wb2.Close()
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
# no $ needed on variable name in Remove-Variable call
Remove-Variable excel
I am trying to automate the following manual task, and am struggling with part of it:
1) Open a text file that contains multiple lines containing data.
2) Copy the contents of this file to the clipboard.
3) Open and Excel spreadsheet.
4) Rename the spreadsheet to Test.
5) Paste the contents of the clipboard.
When this is done manually the content is pasted and each line in the text file is inserted as a new row in column A.
Originally the customer wanted all of the file content to be injected into cell A1. I was able to achieve this with the below PowerShell code.
However they have since changed this back to wanting each line of text to go into a separate row in column A.
I cannot figure out how to do this gracefully via the Get-Content method of copying out the text data. I have seen workarounds to this issue whereby Excel opens the text file and copies the text into an intermediate workbook and then into the final workbook.
Could someone please let me know if it's possible to amend my already working code below so that it adds the text to rows in column A rather than to cell A1?
# Clear the screen of any previous text.
cls
$ExcelFile="C:\Users\User\Desktop\Test\Test.xlsx"
$TextFile="C:\Users\User\Desktop\Test\TestText.txt"
$Content = Get-Content $TextFile -Raw
# Perform operations in Excel based on content of the downloaded file.
$Excel = New-Object -ComObject Excel.Application
# For troubleshooting enable the below to view Excel as file is manipulated:
#$Excel.Visible=$true
# Disable Excel alerts. Hash this line out for troubleshooting.
$Excel.DisplayAlerts = $false
# Set up workbook...
$Workbook = $Excel.Workbooks.Add()
$Data = $Workbook.Worksheets.Item(1)
$Data.Name = 'Test'
# Insert Data
$Data.Cells.Item(1,1) = "$Content"
# Format, save and quit excel
$UsedRange = $Data.UsedRange
$UsedRange.EntireColumn.AutoFit() | Out-Null
$Workbook.SaveAs("$ExcelFile")
$Excel.Quit()
I know that the part I would need to change is as follows, but I'm not sure what to change it to:
# Insert Data
$Data.Cells.Item(1,1) = "$Content"
Many thanks in advance.
To do this, you need to find the last used row in the sheet and write each line from there:
$ExcelFile = "C:\Users\User\Desktop\Test\Test.xlsx"
$TextFile = "C:\Users\User\Desktop\Test\TestText.txt"
# Perform operations in Excel based on content of the downloaded file.
$Excel = New-Object -ComObject Excel.Application
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
# open the file and select the first worksheet
$WorkBook = $Excel.Workbooks.Open($ExcelFile)
$WorkSheet = $Workbook.Worksheets.Item(1)
# get the first unused row
$row = ($WorkSheet.UsedRange.Rows).Count + 1
# fill in the data
Get-Content -Path $TextFile | ForEach-Object {
$WorkSheet.Cells.Item($row++, 1) = $_
}
# format column A and save the file
$UsedRange = $WorkSheet.UsedRange
$UsedRange.EntireColumn.AutoFit() | Out-Null
$WorkBook.Save()
# quit excel and clean up the used COM objects
$Excel.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WorkSheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WorkBook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
I think the solution would be to read each line in content by for or foreach loop
in loop, write the line's content into the last row of column A in the excel file.
It's will be something like this
foreach($line in $Content){
$Data.Cells.Item($LastRow,1) = $line
}
I'm trying to count the cell number of the first row (A1-D1) which is known as header and get that count as the counter.
As all the while find most of them using Usedrange to count the columns:
$headercolcount=($worksheet.UsedRange.Columns).count
But UsedRange will capture maximum count in the whole activesheet, which resulting not identical to the column count in first row if there is extra content data below the header.
I only wish to grab just the first row:
[]
Update:
For clearer view, here is an example.
As 1F & 1G there are no value present, so the answer should be 5 as 1A-1E as it contains data. So how should I grab the 5 correctly?
[]
Get-Process excel | Stop-Process -Force
# Specify the path to the Excel file and the WorkSheet Name
$FilePath = "C:\temp\A_A.xlsx"
$SheetName = "Blad1" # In english this is probably Sheet1
# Create an Object Excel.Application using Com interface
$objExcel = New-Object -ComObject Excel.Application
# Disable the 'visible' property so the document won't open in excel
$objExcel.Visible = $false
$objExcel.DisplayAlerts = $false
# Open Excel file and in $WorkBook
$WorkBook = $objExcel.Workbooks.Open($FilePath)
# Load WorkSheet 'Blad 1' in variable Worksheet
$WorkSheet = $WorkBook.sheets.item($SheetName)
$xlup = -4162
$lastRow = $WorkSheet.cells.Range("A1048576").End($xlup).row
# get the highest amount of columns
$colMax = ($WorkSheet.UsedRange.Columns).count
# initiatie a counter
$count = $null
# set the column you'd like to count
$row = 1
for ($i = 0; $i -le $colMax; $i++){
if($worksheet.rows.Item("$row").columns.Item($i+1).text){
$count++
}
}
$count
This should work. It takes the highest amount of columns. It then loops until it reaches that amount. During the loop it checks if the cell on that row is filled or not, if it is, it adds to the counter.
If you have millions of lines, this might not be the best way but this works for me.
I've testes it with an excel file:
With
$row = 1 this will give : 5
$row = 2 this will give : 6
$row = 3 this will give : 7
$row = 4 this will give : 8
# Specify the path to the Excel file and the WorkSheet Name
$FilePath = "C:\temp\A_A.xlsx"
$SheetName = "Blad1" # In english this is probably Sheet1
# Create an Object Excel.Application using Com interface
$objExcel = New-Object -ComObject Excel.Application
# Disable the 'visible' property so the document won't open in excel
$objExcel.Visible = $false
$objExcel.DisplayAlerts = $false
# Open Excel file and in $WorkBook
$WorkBook = $objExcel.Workbooks.Open($FilePath)
# Load WorkSheet 'Blad 1' in variable Worksheet
$WorkSheet = $WorkBook.sheets.item($SheetName)
$xlup = -4162
$lastRow = $WorkSheet.cells.Range("A1048576").End($xlup).row
$amountofcolumns = $worksheet.UsedRange.Rows(1).Columns.Count
#OUTPUT
write-host "Last Used row:" $lastRow
Write-host "Amount of columns" $amountofcolumns
#show all columnnames
for($i = 1 ; $i -le $amountofcolumns; $i++){
$worksheet.Cells.Item(1,$i).text
}
This will show you how many rows you have AND will show you all values in the first row , ergo your titles.
I am attempting to automate the process of adding a worksheet (with data) per clientname in excel for a monthly report type workbook
I thought it should be straight forward... but the method I am using isnt working.... it doesn't even get to the sheet making mode... can you help me figure out what I did wrong?
The following is the function I made
function Excelclientstatstemplate ($clients) {
$Exl = New-Object -ComObject "Excel.Application"
$Exl.Visible = $false
$Exl.DisplayAlerts = $false
$WB = $Exl.Workbooks.Open($excelmonthlysummary)
$clientws = $WB.worksheets | where {$_.name -like "*$clients*"}
#### Check if Clients worksheet exists, if no then make one with client name ###
$sheetcheck = if (($clientws)) {} Else {
$WS = $WB.worksheets.add
$WS.name = "$clients"
}
$sheetcheck
$WB.Save
# Enter stat labels
$clientws.cells.item(1,1) = "CPU Count"
$clientws.cells.item(2,1) = "RAM"
$clientws.cells.item(3,1) = "Reserved CPU"
$clientws.cells.item(4,1) = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2) = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2) = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2) = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2) = [decimal]$cstats.resmemoryLimitGB
$WB.save
$Exl.quit()
Stop-Process -processname EXCEL
Start-Sleep -Seconds 1
Echo "$clients excel sheet in monthly summary is done.."
}
and then I tried to make a Foreach thing for it
$clientxlmonthlywrite = Foreach ($client in $clientlist){
$cstats = $Combinedstats | Where {$_.Group -eq "$client"}
Excelclientstatstemplate -clients $client
}
The entire Process of the function goes
Take client name
Open a particular excel workbook
Check if there are any sheets with client name
If there are NO sheets with client name, make one with client name
Fill The first column Cells with labels
Fill the second column cells with data (data works I already write CSVs withem)
Save and exit
The Foreach variable just does the function for each of Clients names from a clientlist (nothing wrong with clientlist)
Am I messing something up?
Thanks for the help.
You are not calling the .Add() method correctly. You are missing the parenthesis at the end of it. To fix it you should be able to simply modify the line to this:
$WS = $WB.worksheets.add()
Also, the cells have properties that you should refer to, so I would also modify the part that sets your cell values to something like this:
# Enter stat labels
$clientws.cells.item(1,1).value2 = "CPU Count"
$clientws.cells.item(2,1).value2 = "RAM"
$clientws.cells.item(3,1).value2 = "Reserved CPU"
$clientws.cells.item(4,1).value2 = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2).value2 = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2).value2 = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2).value2 = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2).value2 = [decimal]$cstats.resmemoryLimitGB
I'm fairly sure that defining the type is pointless, since to Excel they're all strings until you set the cell's formatting settings to something else. I could be wrong, but that is the behavior that I have observed.
Now, for other critiques that you didn't ask for... Don't launch Excel, open the book, save the book, and close Excel for each client. Open Excel once at the beginning, open the book, make your updates for each client, and then save, and close.
Test to see if the client has a sheet, and add it if needed, then select the client's sheet afterwords. Right now there's nothing there to set $clientws if you have to add one for that client.
Adding a worksheet by default places it before the active worksheet. This was a poor choice in design in my opinion, but it is what it is. If it were me I'd add new sheets specifying the last worksheet in the workbook, which will add the new worksheet before the last one, making it the second to the last worksheet. Then I'd move the last worksheet up in front of the new one, effectively adding the new worksheet as the last one listed. Is it possible to add the new worksheet as the last one when you make it? Yes, but it's was too complicated for my taste. See here if you are interested in doing that.
When testing for an existing client worksheet to make one if it is missing, do that, don't tell it to test for something, and do nothing, and put everything you want in an Else statement. That just complicates things. All that said, here's some of those suggestions put into practice:
function Excelclientstatstemplate ($clients) {
#### Check if Clients worksheet exists, if no then make one with client name ###
if (($clients -notin $($WB.worksheets).Name)){
#Find the current last sheet
$LastSheet = $WB.Worksheets|Select -Last 1
#Make a new sheet before the current last sheet so it's near the end
$WS = $WB.worksheets.add($LastSheet)
#Name it
$WS.name = "$clients"
#Move the last sheet up one spot, making the new sheet the new effective last sheet
$LastSheet.Move($WS)
}
#Find the current client sheet regardless of if it existed before or not
$clientws = $WB.worksheets | where {$_.name -like "*$clients*"}
# Enter stat labels
$clientws.cells.item(1,1).value2 = "CPU Count"
$clientws.cells.item(2,1).value2 = "RAM"
$clientws.cells.item(3,1).value2 = "Reserved CPU"
$clientws.cells.item(4,1).value2 = "Reserved RAM"
### Put in Values in the next column ###
$clientws.cells.item(1,2).value2 = [int]($cstats.cpuAllocationGHz/2)
$clientws.cells.item(2,2).value2 = [decimal]$cstats.memoryLimitGB
$clientws.cells.item(3,2).value2 = [int]($cstats.rescpuAllocationGHz/2)
$clientws.cells.item(4,2).value2 = [decimal]$cstats.resmemoryLimitGB
Start-Sleep -Seconds 1
Echo "$clients excel sheet in monthly summary is done.."
}
$Exl = New-Object -ComObject "Excel.Application"
$Exl.Visible = $false
$Exl.DisplayAlerts = $false
$WB = $Exl.Workbooks.Open($excelmonthlysummary)
$clientxlmonthlywrite = Foreach ($client in $clientlist){
$cstats = $Combinedstats | Where {$_.Group -eq "$client"}
Excelclientstatstemplate -clients $client
}
$WB.save
$Exl.quit()
Stop-Process -processname EXCEL
This should be pretty simple. I'm looking to take the information from my previous question(s), which is a CSV, and place them in an existing Excel document.
Here's the existing data (in a CSV):
SO | Status | ElapsedHrs
PMTT12345678 Hit on Debra 2.5
PMTS23456789 Get rejected 4.25
PMTT87654321 Send some faxes 1.0
So I have an existing Excel sheet, where all of the SO category needs to go to column K starting at cell 6, Status goes to L starting at 6 and ElapsedHrs goes to column O starting at cell 6.
Using this and a few others as examples, but I can't figure out the syntax. Any help is appreciated, again.
Edit
So far I have this:
$Excel = New-Object -ComObject excel.application
$Excel.visible = $false
$WorkBook = $objExcel.Workbooks.Open($ExportCsv)
$WorkBook2 = $excel.Workbooks.open($Template)
$Worksheet = $Workbook.WorkSheets.item(“$ExpCsvShort”)
$ExportCsv is the name of the CSV with the full path. $ExpCsvShort' is just the filename (the name changes based on the hour and date). $Template` is the template .xslx file to which the data would be written.
$range = $WorkSheet.Range(“A2”).EntireColumn
$range.Copy() | out-null
Not sure what this should be, as I want column A (minus the header) to go to K on $Template starting at 6. Then I want C to start at L6 and D to start at O6, but I don't know the syntax.
Once I have that:
$Worksheet2 = $Workbook2.Worksheets.item(“Worklog”)
$worksheet2.activate()
$range2 = $Worksheet2.Range(“K6:K6”)
$Worksheet2.Paste($range2)
$workbook2.SaveAs($WorkLogSave)
$workbook.close($false)
$Excel.Quit()
[gc]::collect()
[gc]::WaitForPendingFinalizers()
But again, I don't know the syntax for the range here, either.
Edit 2
Here's what I ended up doing.
$Excel = New-Object -ComObject excel.application
$Excel.visible = $true
$WorkBook = $excel.Workbooks.Open($ExportCsv)
$WorkBook2 = $excel.Workbooks.open($Template)
$Worksheet = $Workbook.WorkSheets.item($ExpCsvShort)
$Worksheet.activate()
#A Range Copy
$rangeAc = $WorkSheet.Range(“A2:A26”)
$rangeAc.Copy() | out-null
#Select sheet 2
$Worksheet2 = $Workbook2.Worksheets.item(“Worklog”)
$worksheet2.activate()
#A Range Paste
$rangeAp = $Worksheet2.Range(“K6:K30”)
$Worksheet2.Paste($rangeAp)
#C Range Copy
$rangeCc = $WorkSheet.Range(“C2:C26”)
$rangeCc.Copy() | out-null
#C Range Paste
$rangeCp = $Worksheet2.Range(“O6:O30”)
$Worksheet2.Paste($rangeCp)
#D Range Copy
$rangeDc = $WorkSheet.Range(“D2:D26”)
$rangeDc.Copy() | out-null
#D Range Paste
$rangeDp = $Worksheet2.Range(“L6:L30”)
$Worksheet2.Paste($rangeDp)
$workbook2.SaveAs($WorkLogSave)
$workbook2.close($true)
$workbook.close($true)
$Excel.Quit()
[gc]::collect()
[gc]::WaitForPendingFinalizers()
Probably a pretty crappy way to do it, but it works. Also, I still have EXCEL.EXE running after everything is closed. I've read about 5 ways to kill the process, but I'm worried if I do I'll mess someone up who has another document open, so maybe I can -passthru and capture the .id and kill that instead, but I'll worry about that later I guess.
Thanks for helping, all.
And I'd love to use that gravity, but I'm not sure I understand how. Thanks!