I am currently working on a powershell script that converts excel files from .xls to .xlsx
To be precise, I need this to work in some ways:
I need to catch the .xls files FROM a folder and make's a copy to a backup folder
converts them to .xlsx and uploads them to upload folder
Converting them from a folder and uploading them to another folder work's fine, but I tried to add some features and now I'm stuck.
This is the error when I try to run:
At C:\Users\Test\Conv_XLS_V2.ps1:40 char:2
+ }
+ ~ The Try statement is missing its Catch or Finally block. At C:\Users\Test\Conv_XLS_V2.ps1:20 char:16
+ ForEach-Object { ~
Missing closing '}' in statement block or type definition.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingCatchOrFinally
My code:
# set folders
$downloadfolder = "C:\Users\Test"
#$downloadfolder = "folder that gets the .xls files"
$uploadfolder = "C:\Users\Test\Upload"
#$uploadfolder = "folder that uploads the .xlsx files"
$backupfolder = "C:\Users\Test\Backup"
#$backupfolder = "folder that has .xls files as backup"
#open and convert xls to xlsx
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
write-host $xlFixedFormat
$excel = New-Object -ComObject excel.application
$excel.visible = $true
$filetype ="*xls"
Get-ChildItem -Path $folderpath -Include $filetype -recurse |
ForEach-Object {
try {
$xlsfilename = $_.fullname
#copy file to backup folder
Copy-Item -Path $xlsfilename -Destination $backupfolder
# open the xls
Write-Output "Converting $xlsfilename"
$workbook = $excel.workbooks.open($xlsfilename)
# save converted file (as xlsx)
$xlsxfilename = $xlsfilename + "x"
$workbook.saveas($xlsxfilename, $xlFixedFormat)
$workbook.close()
#remove old file
Write-Output "delete & move file(s)"
Remove-Item -Path $xlsfilename -Force
Move-Item -Path $xlsxfilename -Destination $uploadfolder -Force
# garbage collection
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
# close excel
$excel.Quit()
$excel = $null
Can someone have a look please?
The error message is clear. You forgot to close the try{..} block with an ending bracket } and a try{..} should be followed up by either one or more catch{..} blocks and optionally a finally{..} block.
You can read about that on about Try Catch Finally.
Then, there are some other things wrong and/or can be improved upon in your code as well.
$folderpath is not defined and should be the source folder $downloadfolder
use -Filter instead of -Include as it is much faster. Also you have left out the dot in '*.xls'
append switch -File to the Get-ChildItem cmdlet to make sure you will not receive and try to process directories as well
you can save the converted .xlsx files directly to the uploadfolder, no need to create first and then move
to remove the used COM objects, release them from memory first and then initiate the Garbage Collect.
Do this after you have quit Excel.
# set folders
$downloadfolder = "C:\Users\Test" # folder where the .xls files are
$uploadfolder = "C:\Users\Test\Upload" # folder that uploads the .xlsx files
$backupfolder = "C:\Users\Test\Backup" # folder that has .xls files as backup
# open and convert xls to xlsx
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false # it is much faster if Excel is not visible
# loop through the .xls files and process them
Get-ChildItem -Path $downloadfolder -Filter '*.xls' -Recurse -File |
ForEach-Object {
try {
$xlsfilename = $_.FullName
#copy file to backup folder
Copy-Item -Path $xlsfilename -Destination $backupfolder -Force
# open the xls
Write-Host "Converting $xlsfilename"
$workbook = $excel.Workbooks.Open($xlsfilename)
# save converted file (as xlsx) directly to the upload folder
$newfilename = Join-Path -Path $uploadfolder -ChildPath ('{0}.xlsx' -f $_.BaseName)
$workbook.SaveAs($newfilename, $xlFixedFormat)
$workbook.Close()
#remove old file
Write-Host "Delete old file '$xlsfilename'"
Remove-Item -Path $xlsfilename -Force
}
catch {
# write out a warning as to why something went wrong
Write-Warning "Could not convert '$xlsfilename':`r`n$($_.Exception.Message)"
}
}
# close excel
$excel.Quit()
# garbage collection
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
The error describe a syntax issue. You have included a try { statement without closing it with a } catch {} block. That's all.
Related
Im trying to make a script which makes excel sheets into PDF's, and save using a name it gets from one of the value in the sheet. So far i got this:
PS-script
Im getting a error and it doesnt save using the specifik name. Im able to print the value though and the PDF are created, so somethings working.
Error:
Join-Path : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ChildPath'. Specified
method is not supported.
At line:26 char:51
+ $filepath = Join-Path -Path $savepath -ChildPath ($value + ".pdf")
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Join-Path], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.JoinPathCommand
Bonus question: If i want it to move the excel after, what would i need to add, and where, as i plan on making this run daily and convert all the new files only.
Code:
#UPDATE DATA IN EXCEL FILES
#THEN CREATE PDF FILE
[string]$path = "C:\Users\PCUser\Desktop\JCI Praktik\PDF" #Path to Excel spreadsheets to save to PDF
[string]$savepath = "C:\Users\PCUser\Desktop\JCI Praktik\PDF"
[string]$dToday = Get-Date -Format "yyyyMMdd"
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -as [type]
$excelFiles = Get-ChildItem -Path $path -include *.xls, *.xlsx -recurse
# Create the Excel application object
$objExcel = New-Object -ComObject excel.application
$objExcel.visible = $false #Do not open individual windows
foreach($wb in $excelFiles)
{
# Path to new PDF with date
# Open workbook - 3 refreshes links
$workbook = $objExcel.workbooks.open($wb.fullname, 3)
$worksheet = $objExcel.worksheets.item(1) #SK
$value = $objExcel.Cells.Item(1, 6)
$value.Text #this should give you back the Value in that Cell
$workbook.RefreshAll()
$filepath = Join-Path -Path $savepath -ChildPath ($value + "_" + $dtoday + ".pdf")
# Give delay to save
Start-Sleep -s 5
# Save Workbook
$workbook.Saved = $true
"saving $filepath"
#Export as PDF
$worksheet.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $filepath) #SK
$objExcel.Workbooks.close()
}
$objExcel.Quit()```
Change to
$value = $objExcel.Cells.Item(1, 6).Text # You can also use '.Value' or '.Value2'
This could however easily result in a string having invalid filename characters, so to remove these use
$value = $value -replace ("[{0}]" -f [regex]::Escape([IO.Path]::GetInvalidFileNameChars() -join ''))
Next, join into a complete filepath
$filepath = Join-Path -Path $savepath -ChildPath ('{0}_{1}.pdf' -f $value, $dtoday)
The $value variable needs to be a string,
You are using these two lines in your script,
$value = $objExcel.Cells.Item(1, 6)
$value.Text #this should give you back the Value in that Cell
so in the filepath variable
$filepath = Join-Path -Path $savepath -ChildPath ($value + ".pdf")
what is the output of $value.text? this would be a string, which should work.
I'm currently working on Powershell to handle excel files(*.xlsm).
The problem is the code below can only read "test.xlsm".
When the name is not test like "this.xlsm" , that code cannot read the file.
Any help...?
Thanks for your answer in advance :)
$destination = "C:\JJ\"
$dirName = Get-ChildItem -Name -Filter *.xlsm
$saveAs = $destination + "new\"
foreach($z in $dirName){
$excel=New-Object -ComObject Excel.Application
$excel.visible=$false
$excel.Displ`ayAlerts=$false
$book=$excel.Workbooks.Open($destination + $z)
$sheet=$book.Worksheets.item(1)
$sheet.Cells.Item(1,5)="=max(B2:B6)"
$book.SaveAs($saveAs + $z)
$excel.Quit()
$excel=$null
}
You are using confusing variable names in your code.. Why call the source path $destination??
Anyway, you should use the -File switch on your Get-ChildItem cmdlet to make sure this will only return FileInfo objects, not DirectoryInfo objects aswell. (these are Objects, not strings)
Then, there is a better way to construct paths. Use Join-Path instead of concatenating things like with $destination + $z.
Lastly, I would create the Excel object only once, before the loop and cleanup memory afterwards. Now, you are creating new COM objects in every iteration and never release them from memory.
Below code should do what you intend:
$source = "C:\JJ"
$destination = Join-Path -Path $source -ChildPath 'new'
# test if the destination path already exists and if not, create it
if (!(Test-Path -Path $destination -PathType Container)) {
$null = New-Item -Path $destination -ItemType Directory
}
# create the Excel COM object outside the loop
$excel = New-Object -ComObject Excel.Application
$excel.visible = $false
$excel.DisplayAlerts = $false
# get all *.xlsm files inside the source folder
Get-ChildItem -Path $source -Filter '*.xlsm' -File | ForEach-Object {
# The $_ automatic variable represents 1 FileInfo object in each iteration
$book = $excel.Workbooks.Open($_.FullName)
$sheet = $book.Worksheets.item(1)
$sheet.Cells.Item(1,5) = "=max(B2:B6)"
# join the destination path and the file name for output
$saveAs = Join-Path -Path $destination -ChildPath $_.Name
$book.SaveAs($saveAs)
$book.Close()
}
# cleanup Com objects
$excel.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($book)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
$excel = $null
Note, If you have PowerShell version below 3.0, the -File switch is not available. Instead, then use
Get-ChildItem -Path $source -Filter '*.xlsm' | Where-Object {!$_.PSIsContainer} | ForEach-Object { ... }
# remove -name to get a directory object instead of a string
$dirName = Get-ChildItem -Filter *.xlsm
# you need the file name from the directory object listing
foreach($z in $dirName.name){ ...... }
I am trying to have Powershell copy, rename than edit a excel file. It copies and renames the file as intended however when I go to open the file with excel it is unable to find the file. See attached code.
Thank you for the help.
#Export Textbox outputs
$S0 = $textBox1.Text
$jobname = $textBox2.Text
$contractor = $TextBox3.Text
#combine textbox outputs
$folder = "$S0" + "_" + "$jobname" + "_" + "$contractor"
$subsubfolder = ".\"+"$folder" + "\Dir"
$takeoffname = "$s0" + "_takeoff.xlsx"
#Excel
$xl = New-Object -ComObject excel.application
Start-Sleep -Seconds 5
$xl.Visible = $true
Start-Sleep -Seconds 5
$wb = $xl.Workbooks.Open("$subsubfolder\$takeoffname")
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = "$jobname"
$data.Cells.Item(1,7) = "$S0"
$wb.Save()
$xl.Quit()
NEW updated Code - Added Join path and It broke the create folder loop. Sorry IF the added requirement to make the folder creates extra problems.
$S0 = $TextBox1.Text
$jobname = $TextBox2.Text
$contractor = $TextBox3.Text
$folder = ' {0}_{1}_{2}' -f $S0, $jobname, $Contractor
$file = '{0}_takeoff.xlsx' -f $S0
$PILname = 'PIL_{0}.xlsx' -f $S0
Write-host $folder
New-Item -ItemType Directory "./$folder"
foreach($line in Get-Content $Filenames)
{
New-Item $folder\$line -ItemType Directory
}
$subfolder = '{0}\1 - Estimating Original Quote Material' -f $folder
$subsubfolder = Join-Path -Path $PWD - ChildPath $Subfolder
$filePath = Join-Path -Path $PWD -ChildPath (Join-Path -Path $subsubfolderfolder -ChildPath $file)
$PILpath = Join-Path -Path $PWD -ChildPath (Join-Path -Path $subsubfolderfolder -ChildPath $PILname)
Write-host $filePath
Write-host $subsubfolder
pause
#Copy Files
Copy-Item '.\_master_takeoff.xlsx' "$subsubfolder\_master_takeoff.xlsx"
Copy-Item '.\PIL_S0XXXXX .xlsx' $subsubfolder
#Rename Files
Rename-Item -Path "$subsubfolder\_master_takeoff.xlsx" -newname $takeoffname
Rename-Item -Path "$subsubfolder\PIL_S0XXXXX .xlsx" -newname $PILpath
$xl = New-Object -ComObject excel.application
Start-Sleep -Seconds 5
$xl.Visible = $true
Start-Sleep -Seconds 5
$wb = $xl.Workbooks.Open("$subsubfolder\$takeoffname")
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = "$jobname"
$data.Cells.Item(1,7) = "$S0"
$wb.Save()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
By taking strings off textboxes and combining that to a file path with string concatenation, you're bound to end up with a path that doesn't exist.
Having said that, the error comes from using the .\ in the path.
Powershell may know where that is, but Excel will have no idea where to look for the file. Excel has its own Default path, usually pointing to the Documents folder and when given relative paths, it will use that.
Always use existing, absolute file paths for opening stuff in external applications.
Better use something like this
#Export Textbox outputs
$prefix = $TextBox1.Text
$jobname = $TextBox2.Text
$contractor = $TextBox3.Text
#combine textbox outputs to form the directory (I like using the -f format operator)
$file = '{0}_takeoff.xlsx' -f $prefix
$folder = '{0}_(1}_{2}\Dir' -f $prefix, $jobname, $contractor
$filePath = Join-Path -Path $PWD -ChildPath (Join-Path -Path $folder -ChildPath $file)
# test if the file can be founc
if (Test-Path $filePath -PathType Leaf) {
$xl = New-Object -ComObject excel.application
$xl.Visible = $true
$wb = $xl.Workbooks.Open($filePath)
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = $jobname
$data.Cells.Item(1,7) = $prefix
$wb.Save()
$xl.Quit()
# important: clean-up COM objects after use
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
else {
Write-Warning "File '$filePath' not found"
}
Instead of using $PWD (Print Working Directory) you can also use Get-Location which in fact is the same thing
Since I have no idea why your updated code is creating subfolders, I'll leave that out here.
Please look at how the -f Format operator works because now you're doing that wrong.
Also, to not confuse the working directory for PowerShell and the default path for Excel anymore, define the full root path first in the code. Below I'm using a variable called $workingDir for that.
Copy-Item can copy and rename at the same time.
# let's forget about the 'Set-Location' and use absolute paths from the beginning
$workingDir = '\\Server\Share\Folder' # set this to the real path
# Export Textbox outputs
$S0 = $TextBox1.Text
$jobname = $TextBox2.Text
$contractor = $TextBox3.Text
# combine textbox outputs to form the directory (I like using the -f format operator)
$PILname = 'PIL_{0}.xlsx' -f $S0
$file = '{0}_takeoff.xlsx' -f $S0
$folder = '{0}_(1}_{2}\1 - Estimating Original Quote Material' -f $S0, $jobname, $contractor
$folderPath = Join-Path -Path $workingDir -ChildPath $folder # --> Full absolute path to the working folder
$filePath = Join-Path -Path $folderPath -ChildPath $file # --> Full absolute path to the file
Write-host $filePath
Write-host $folderPath
#Copy and rename master Files
$masterFile = Join-Path -Path $workingDir -ChildPath '_master_takeoff.xlsx'
$pilFile = Join-Path -Path $workingDir -ChildPath 'PIL_S0XXXXX.xlsx'
Copy-Item -Path $masterFile -Destination $filePath
Copy-Item -Path $pilFile -Destination (Join-Path -Path $folderPath -ChildPath $PILname)
############################
#Write to new take off file
############################
# Call excel and open file
$xl = New-Object -ComObject excel.application
$xl.Visible = $true
$wb = $xl.Workbooks.Open($filePath)
$data = $wb.Worksheets.Item("Storm")
$Data.Cells.Item(1,2) = $jobname
$data.Cells.Item(1,7) = $S0
$wb.Save()
$xl.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($wb) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl) | Out-Null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Everything seems ok with the code, but you are using relative paths.
If you are doing that, you need to change the working directory before opening the excel.
Ex: Set-Location C:\
I have a software that just allows me to download my data in xls files but I want to use it as an xlsx file.
Currently I have an excel macro when I click on a button it converts all my xls files in xlsx but I want to automate this task so I don't have to open the excel file and click on the button.
I was thinking of a script that start when I log in windows or something like that, and it converts automatically my xls file when I download it. But I'm not very good with scripts so anyone can help me with that ? It's on windows 7 and 10.
Thank you for your help.
Edit:
Here is my Powershell script, now I have to automate it so that it runs automatically when I download a new .xls file, I know I can use the task scheduler but how can I do that automation on en event like adding a new xls file to a folder ? Or maybe we can do it in powershell ?
My script:
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlOpenXMLWorkbook
write-host $xlFixedFormat
$excel = New-Object -ComObject excel.application
$excel.visible = $false
$folderpath = "C:\Users\Mgtspare\Downloads\"
$filetype ="*xls"
Get-ChildItem -Path $folderpath -Include $filetype -recurse |
ForEach-Object `
{
$path = ($_.fullname).substring(0, ($_.FullName).lastindexOf("."))
"Converting $path"
$workbook = $excel.workbooks.open($_.fullname)
$path += ".xlsx"
$workbook.saveas($path, $xlFixedFormat)
$workbook.close()
remove-item $_.fullname
}
$excel.Quit()
$excel = $null
[gc]::collect()
[gc]::WaitForPendingFinalizers()
UPDATE:
I changed my script to have a new script faster and I put a watcher so I run the script when a new xls file is downloaded, I will use task manager to run this script when I log in windows so it can watch without doing anything.
Here is my new script:
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Users\Mgtspare\Downloads"
$watcher.Filter = "*.xls"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = {
$watcher.Path *.xls | rename-item -newname { [io.path]::ChangeExtension($_.name, "xlsx") }
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}
Issue:
My script run in the ISE but when i want to run it in cmd or with the right click on my script file and run with with powershell I have this issue
You must provide a value expression on the right-hand side of the '*' operator.
The below script automatically runs each morning with Task Manager
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Users\Mgtspare\Downloads"
#$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = {
Get-ChildItem -Path C:\Users\Mgtspare\Downloads *.xls | rename-item -newname { [io.path]::ChangeExtension($_.name, "xlsx") }
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher 'Created' -SourceIdentifier 'FileCreated' -Action $action
while ($true) {sleep 1000}
I am a novice to PowerShell and have been working on the following script to look through a directory for XLS and XLSX files. Afterwards, it would get the creation date of each file and rename the filename with the creation date appended to the end.
This script works fine for XLSX files. However when XLS files are encountered, the is save prompt: "Want to save your changes to xxx.xls?"
How can I get rid of this save prompt. Below is my code. Thank you:
Param(
$path = "C:\Excel",
[array]$include = #("*.xlsx","*.xls")
)
$application = New-Object -ComObject Excel.Application
$application.Visible = $false
$binding = "System.Reflection.BindingFlags" -as [type]
[ref]$SaveOption = "microsoft.office.interop.Excel.WdSaveOptions" -as [type]
## Get documents
$docs = Get-childitem -path $Path -Recurse -Include $include
foreach($doc in $docs)
{
try
{
## Get document properties:
$document = $application.Workbooks.Open($doc.fullname)
$BuiltinProperties = $document.BuiltInDocumentProperties
$pn = [System.__ComObject].invokemember("item",$binding::GetProperty,$null,$BuiltinProperties,"Creation Date")
$value = [System.__ComObject].invokemember("value",$binding::GetProperty,$null,$pn,$null)
## Clean up
$document.close([ref]$saveOption::wdDoNotSaveChanges)
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($BuiltinProperties) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($document) | Out-Null
Remove-Variable -Name document, BuiltinProperties
## Rename document:
$date=$value.ToString('yyyyMMdd');
$strippedFileName = $doc.BaseName;
$extension = $doc.Extension;
#write-host $strippedFileName;
$newName = "$strippedFileName" +"_" + "$date"+ "$extension";
write-host $newName;
Rename-Item $doc $newName
}
catch
{
write-host "Rename failed."
$_
}
}
$application.quit()
$application.Workbooks.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($application) | Out-Null
According to this old kb article, you can trick excel into not prompting you by setting the Saved property on the workbook to true, so I would try:
$document.Saved = $true
$document.close([ref]$saveOption::wdDoNotSaveChanges)