I am trying replicate an end users experience by monitoring the time it takes to copy a large file off of a network directory.
I'm using Measure-Command to find the total time it takes to copy an item from a network directory onto the computer that this script is scheduled on (using Windows Scheduler) and output the time in an xlsx.
The issue I'm running into is that every time this script runs off of the scheduler (daily), it overwrites the previous day's data instead of posting the result in the next cell. When I run it manually multiple times, it works just fine and posts separate results under each other. I think the issue is that it's going by the instance (so running the code a handful of times in the same PowerShell instance sees that the $previousRow works, but in the daily schedule it opens a new instance every time and writes over the old data in cell (1,1) and (1,2).
Any suggestions on how to keep historical data?
$seconds = Measure-Command {
Copy-Item -Path X:\shareddrive\test.pdf -Destination C:\Users\Me\Desktop
} | select TotalSeconds
$erroractionpreference = "SilentlyContinue"
$a = New-Object -ComObject Excel.Application
$dt = Get-Date -Format "MM/dd/yyyy hh:mm:ss"
$a.Workbooks.Open("X:\shareddrive\output\timesample.xlsx")
$a.Visible = $true
$a.Worksheets.Item(1)
$previousRow += 1
$a.Cells.Item($previousRow,1) = "OfficeLocation - " + $dt
$a.Cells.Item($previousRow,2) = $seconds.TotalSeconds
$a.ActiveWorkbook.Save()
$a.Workbooks.Close()
$a.Quit()
Remove-Item -Path C:\Users\Me\Desktop\test.pdf
Not sure if you haven't copied all your code in? But it looks like you aren't actually defining $previousRow anywhere? So your existing code runs $previousRow += 1 and that sets $previousRow to 1... which means each time the code runs it will hit row 1 first
To be able to find the row that has the last information in it (ie the value to set $previousValue to + 1) you can use this code:
$filepath = "C:\Folder\ExcelFile.xlsx"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $False
$WorkBook = $objExcel.Workbooks.Open($filepath)
$WorkSheet = $objExcel.WorkSheets.item(1)
$WorkSheet.activate()
[int]$lastRowvalue = ($WorkSheet.UsedRange.rows.count + 1) - 1
$lastrow = $WorkSheet.Cells.Item($lastRowvalue, 1).Value2
write-host $previousValue
write-host $lastRowvalue
Copy/paste from here (with a few slight modifications): To get the value of last cell used in Excel
That tells you the last row with data... so you would need to add 1 to that first before you set it as the value for $previousRow:
$previousRow = $previousRow + 1
Related
I am trying to save a specific Excel Sheet from a Macro Enabled Excel Workbook (xlsm) via Powershell to csv to upload it into a database. This is done via Powershell since it needs to be automated along with some more data processing etc.
The Situation:
I have a list of excel files in a directory, each having the same structure/sheets.
I parse each file to a Powershell function which creates a new Excel Object and opens the workbook.
In the next step I am trying to save a specific sheet (here Sheet with Index 2)
The Problem:
Iterating through each Worksheet and saving them gives me all Sheets including the one I am looking for (Sheet 2)
Accessing Sheets 2 via $ws = $wb.Worksheets(2) also gives me the right Sheet (according $ws.name) but saving $ws via $ws.SaveAs("$destinationDirectory" + $File.BaseName + ".csv", 6) results in a csv file containing Sheet 1.
I have saved Worksheets with basically exactly the same code successfully before (except instead of xlsm I was dealing with xlsx).
Code
Function ExcelToCsv ($File) {
echo "Converting $($File.Name) to csv..."
$Excel = New-Object -ComObject Excel.Application;
$Excel.DisplayAlerts = $False;
$wb = $Excel.Workbooks.Open($File)
$ws = $wb.Worksheets(2)
echo "ws is:" + $ws.name # Correctly printing Worksheet name of Sheet 1
$ws.SaveAs("$destinationDirectory" + $File.BaseName + ".csv", 6) # Saving Sheet 1 instead of Sheet 2
$wb.Close($True);
}
}
foreach ($file in $files){
ExcelToCsv -File $file;
}
Workaround
My current workaround is to iterate through the sheets via foreach
$n = 1
foreach($ws in $wb.Worksheets){
$ws.SaveAs("$destinationDirectory" + $File.BaseName + "-$($n).csv", 6)
$n = $n+1
}
And deleting any unwanted sheets (recognized by $n != 2) via
Remove-Item "$($destinationDirectory)*-[13456789].csv";
Which works but is not really optimal.
Also: checking for $n -eq 2 in the foreach and only saving that sheet also does not work since it will simply save sheet 1 again.
And ideas are greatly appreciated!
After a frustrating afternoon I eventually worked this one out - you need to call Activate against the sheet you want to save e.g.
$sheet = $book.sheets.item('User_Specified_Report')
$Sheet.Activate()
$sheet.SaveAs($newName,6)
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 have a PowerShell script which pulls data from DB and pushes it to a excel sheet. I am facing slowness (45 mins approx) while copying the records in the dataset to the excel sheet as the number of records exceed 200K. And I am trying to loop them one by one using the below snippet, which takes more time. Is there a way in which I can transfer the data from dataset to excel more efficiently?
$cells=$Worksheet.Cells
$row=1
foreach ($rec in $dataset.Tables[0].Rows)
{
$row++
$col=1
$cells.item($Row,$col)=$USR.ID
$col++
$cells.item($Row,$col)=$USR.Name
$col++
$cells.item($Row,$col)=$USR.Age
$col++
}
You shoud try PSExcel module. There's no need to create COM object and even have Excel installed. Your example would look like this and be lightning fast:
$dataset.Tables[0] |
Select-Object ID,Name,Age |
Export-XLSX -Path $FullName -AutoFit -WorksheetName 'MyData'
A nice little workaround I saw sometime ago was to format the rows as a CSV string and simply paste them in. For the environment I was using, this proved to be more efficient than creating a file using Export-CSV, then loading it in Excel.
#Row data joined with tabs
$data = #("[A1]", "[A2]", "[A3]", "[A4]", "[A5]", "[A6]") -join "`t"
#Multiple rows joined with new lines
$dataToPaste = "{0}`n{1}`n{2}" -f $data, $data.replace("A", "B"), $data.replace("A", "C")
$excel = New-Object -ComObject Excel.Application
$book = $excel.Workbooks.Add()
$sheet = $book.Worksheets.Add()
#Activate where to put data
$sheet.Range("B2").Activate() | Out-Null
#Copy data to clipboard and paste into sheet.
$dataToPaste | Clip
$sheet.Paste()
$excel.Visible = $true
#Cleanup
[Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null
$excel = $null
I did find that, very rarely, the Paste method throws an error, which was fixed by retrying a second time if it failed:
try{
$sheet.Paste()
}catch{
$sheet.Paste()
}
This may not be a preferred option if you are running something on a PC being used by someone, as the user could copy something to the clipboard after the script does (but before $sheet.Paste()) and invalidate your data.
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!
I am doing data output to csv file via powershell. Generally things goes well.
I have exported the data to csv file. It contains about 10 columns. When I open it with MS Excel it's all contained in first column. I want to split it by several columns programmatically via powershell(same GUI version offers). I could make looping and stuff to split the every row and then put values to appropriate cell but then it would take way too much time.
I believe there should be an elegant solution to make one column split to multiple. Is there a way to make it in one simple step without looping?
This is what I came up with so far:
PS, The CSV file is 100% FINE. The delimiter is ','
Get-Service | Export-Csv -NoTypeInformation c:\1.csv -Encoding UTF8
$xl = New-Object -comobject Excel.Application
$xl.Visible = $true
$xl.DisplayAlerts = $False
$wb = $xl.Workbooks.Open('c:\1.csv')
$ws = $wb.Sheets|?{$_.name -eq '1'}
$ws.Activate()
$col = $ws.Cells.Item(1,1).EntireColumn
This will get you the desired functionality; add to your code. Check out the MSDN page for more information on TextToColumns.
# Select column
$columnA = $ws.Range("A1").EntireColumn
# Enumerations
$xlDelimited = 1
$xlTextQualifier = 1
# Convert Text To Columns
$columnA.texttocolumns($ws.Range("A1"),$xlDelimited,$xlTextQualifier,$true,$false,$false,$true,$false)
$ws.columns.autofit()
I had to create a CSV which had "","" as delimiter to test this out. The file with "," was fine in excel.
# Opens with all fields in column A, used to test TextToColumns works
"Name,""field1"",""field2"",""field3"""
"Test,""field1"",""field.2[]"",""field3"""
# Opens fine in Excel
Name,"field1","field2","field3"
Test,"field1","field.2[]","field3"
Disclaimer: Tested with $ws = $wb.Worksheets.item(1)