I tried this code as follows:
param(
$inputPath = "N:\Disease_Prevention\....\Samplename.xlsx",
$outputPath = "N:\Disease_Prevention\...\Output.csv"
)
$xlCSV=6
$inputPath = (Resolve-path $inputPath).Path
$outputpath = (Resolve-path $outputpath).Path
get-childitem $inputPath -File | foreach {
write-host "processing $_ "
$Excelfilename = $_.fullname
if(!$outputPath)
{
$outputPath = $_.DirectoryName
}
$CSVfilename =join-path $outputpath $_.BaseName
$CSVfilename+=".csv";
#open excel and save
$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
}
}
And received the following error as follows:
The error says "Unable to get the SaveAs property of the Workbook class"
Would you know why it could be?
And if it's a permission issue, then would you recommend an alternative way to convert excel into csv? Should I use cmdlet to read each line at the excel file and convert one by one?
The issues I encountered with your code are
$outputpath = (Resolve-path $outputpath).Path
Presumably this CSV file doesn't exist, so when you try to resolve the path it gives you nothing.
if(!$outputPath)
{
$outputPath = $_.DirectoryName
}
$CSVfilename =join-path $outputpath $_.BaseName
$CSVfilename+=".csv";
If you don't pass in an outputpath argument, then this will build the file properly. However, if you pass in an outputpath as a csvfile, then you build out a path that looks like
"N:\Disease_Prevention\...\Output.csv\inputpath basename.csv"
as you're appending the current file's basename and .csv to an already full path.
The adjusted code below worked fine.
param(
$inputPath = "N:\Disease_Prevention\....\Samplename.xlsx",
$outputPath = "N:\Disease_Prevention\...\Output.csv"
)
$xlCSV=6
$inputPath = (Resolve-path $inputPath).Path
get-childitem $inputPath -File | foreach {
write-host "processing $_ "
$Excelfilename = $_.fullname
if(!$outputPath)
{
$outputPath = $_.DirectoryName
$CSVfilename = join-path $outputpath $_.BaseName
$CSVfilename+=".csv"
}
else{
$CSVfilename = $outputPath
}
#open excel and save
$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
}
}
I also encourage you to check out Doug Finke's ImportExcel powershell module. Makes working with Excel files so much easier and it doesn't require Excel be installed on the machine that runs the script.
Related
I have the following code and it works perfectly except it's not closing Excel properly. It's leaving an Excel process running.
Is there a way to close Excel properly without killing the process?
Since i'm using other Excel files while running this script i can not kill all active Excel processes.
I think i tried everything i found online.
$WorkDir = "D:\Test\QR_ES\RG_Temp"
$BGDir = "D:\Test\QR_ES\3_BG"
$File = "D:\Test\QR_ES\4_Adr_Excel\KD_eMail.xlsx"
$SentDir = "D:\Test\QR_ES\RG_Temp\Sent\Dunning"
chdir $WorkDir
$firstPageList = Get-ChildItem "$WorkDir\1*.pdf" -File -Name
ForEach ($firstPage in $firstPageList)
{
$secondPage = "$BGDir\BG_RG.pdf"
$output = "Dunn-$firstPage"
invoke-command {pdftk $firstPage background $secondPage output $output}}
del 1*.pdf
gci $WorkDir\Dunn-*.pdf | rename-item -newname {$_.Name.Substring(5)} -Force
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $false
$Workbook = $Excel.workbooks.open($file)
$DunnList = Get-ChildItem "$WorkDir\1*.pdf" -File -Name
ForEach ($Dunn in $DunnList)
{
$Worksheets = $Workbooks.worksheets
$Worksheet = $Workbook.Worksheets.Item("KD_eMail")
$Range = $Worksheet.Range("A1").EntireColumn
$DunnSearch = $Dunn.Substring(0,5)
$SearchString = $DunnSearch
$Search = $Range.find($SearchString)
$Recipient = $Worksheet.Cells.Item($Search.Row, $Search.Column + 1)
$Msg = "<span style='font-family:Calibri;font-size:12pt;'>Test</span>"
$Outlook = New-Object -ComObject Outlook.Application
$namespace = $Outlook.GetNameSpace("MAPI")
$namespace.Logon($null, $null, $false, $true)
$EmailFrom = ('test#test.com')
$account = $outlook.Session.Accounts.Item($EmailFrom)
$Mail = $Outlook.CreateItem(0)
$Mail.HTMLBody = $Msg
$Mail.Subject = "OP - $SearchString"
$Mail.To = $Recipient
function Invoke-SetProperty {
param(
[__ComObject] $Object,
[String] $Property,
$Value
)
[Void] $Object.GetType().InvokeMember($Property,"SetProperty",$NULL,$Object,$Value)
}
Invoke-SetProperty -Object $mail -Property "SendUsingAccount" -Value $account
$Mail.Attachments.Add("$WorkDir\$Dunn")
$Mail.Save()
$Mail.close(1)
$Mail.Send()}}
$workbook.close($false)
$Excel.Quit()
chdir $WorkDir
del 1*.pdf
See this post:
https://stackoverflow.com/a/35955339/5329137
which is not accepted as an answer, but I believe is the full, correct way to close Excel.
This is what did it for me:
$FilePID = (Get-Process -name Excel | Where-Object { $_.MainWindowTitle -like 'FileName.xlsx*' }).Id
$Workbook.Save()
$Workbook.close($false)
Stop-Process $FilePID
Elaborating on #ASD's answer, since the MainWindowTitle doesn't (always) include the file suffix (.xlsx) you may have to strip that when comparing it to the filename. I'm using -replace to use a Regex match of everything before the last dot.
$excelPID = (Get-Process -name Excel | Where-Object { $_.MainWindowTitle -eq $fileName -replace '\.[^.]*$', '' }).Id
$workbook.Close()
Stop-Process $excelPID
Sorry for my bad english I'm French.
I'm new at Powershell and I wanted to rename all of my worksheet in multiple files.
So far i got that code that rename all files in a directory
Function Rename()
{
$path = Get-Location
$files = Get-ChildItem
$counter = 1
foreach($file in $files)
{
Rename-Item $file.FullName "$counter" + ".xlsx"
$counter++
}
}
Then I tried on a specific file to rename all of the sheets that are inside but it doesn't work. The loop works only one time while it has 4 worksheets.
Function RenameTab ($ExcelFileName)
{
#Emplacement du fichier
$excelFile = "C:\Users\Donosaure\Documents\Magister\" + $excelFileName + ".xlsx"
#Ouverture d'excel
$xldoc = New-Object -ComObject "Excel.Application"
#Message de confirmation
$xldoc.Visible = $false
$xldoc.DisplayAlerts = $false
#Ouverture du fichier
$workbook = $xldoc.Workbooks.Open($excelFile)
$inc = 1
$i=1
foreach ($worksheet in $workbook.Worksheets.count)
{
$worksheet = $workbook.Sheets.Item($i)
$worksheet.Name = $inc
$inc++
$i++
$workbook.SaveAs("C:\Users\Donosaure\Documents\Magister\1.xlsx")
$workbook.Close()
}
$xldoc.Quit()
}
RenameTab("Magister")
Can somebody help me ?
Thanks for your time
A few remarks about your code:
Parameters sent to a function in PowerShell are separated by space, you should not use brackets around them as in RenameTab("Magister")
When using COM objects, always make sure you release them from memory when done, otherwise they will linger in memory and if you run this again and again, you will run out of resources otherwise
Please check for Excel Worksheets Naming Convention, so you do not create worksheet names with invalid characters. At the moment, that is not the case, but you never know how this evolves.
Please use the PowerShell function 'Verb-Noun' naming convention for functions you create.
Below should do what you want:
Function Rename-ExcelTabs ($ExcelFileName) {
#Emplacement du fichier
$excelFile = "C:\Users\Donosaure\Documents\Magister\" + $excelFileName + ".xlsx"
#Ouverture d'excel
$xldoc = New-Object -ComObject "Excel.Application"
#Message de confirmation
$xldoc.Visible = $false
$xldoc.DisplayAlerts = $false
#Ouverture du fichier
$workbook = $xldoc.Workbooks.Open($excelFile)
for ($i = 1; $i -le $workbook.Worksheets.Count; $i++) {
$workbook.Sheets.Item($i).Name = $i
}
$workbook.SaveAs("C:\Users\Donosaure\Documents\Magister\" + $excelFileName + "_1.xlsx")
$workbook.Close()
$xldoc.Quit()
# clean-up used COM objects
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($xldoc)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
Rename-ExcelTabs "Magister"
As per your comment, the function could be rewritten to not only change the tab names in one Excel file, but process all .xlsx files inside a folder and rename these files aswell.
One way would be do remove the original file after the tabs have been renamed and a new file is created with $workbook.SaveAs(), as in the code above.
The following function does this by renaming the file first and next change the tab names in it.
function Rename-ExcelTabsAndFiles {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ValidateScript({Test-Path -Path $_ -PathType Container})]
[Alias('Path')]
[string]$SourceFolder
)
# get a list of xlsx files
$allFiles = Get-ChildItem -Path $SourceFolder -Filter '*.xlsx' -File
# create an Excel object
$xldoc = New-Object -ComObject Excel.Application
# Message de confirmation
$xldoc.Visible = $false
$xldoc.DisplayAlerts = $false
$fileCount = 1
foreach ($excelFile in $allFiles) {
# rename the file. use -PassThru to get the FileInfo object of the renamed file
# apparently you want the files to be called '1.xlsx', '2.xlsx' etc.
$newName = '{0}.xlsx' -f $fileCount++
Write-Host "Renaming file '$($excelFile.Name)' to '$newName'"
$excelFile = Rename-Item -Path $excelFile.FullName -NewName $newName -PassThru
# Ouverture du fichier
$workbook = $xldoc.Workbooks.Open($excelFile.FullName)
# rename all worksheets in the file
Write-Host "Renaming all worksheets in '$newName'"
for ($i = 1; $i -le $workbook.Worksheets.Count; $i++) {
$workbook.Sheets.Item($i).Name = $i
}
$workbook.Save()
$workbook.Close()
}
$xldoc.Quit()
# clean-up used COM objects
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($xldoc)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
Rename-ExcelTabsAndFiles "C:\Users\Donosaure\Documents\Magister"
I used below code to convert file from CSV to xlsx. But it only convert single file at a time. I want this to convert all the files in directory at a time.
$xl = New-Object -ComObject Excel.Application
$xl.Visible = $true
$Workbook = $xl.Workbooks.Open("$loglocation\errors_$server.csv")
$Worksheets = $Workbooks.Worksheets
$Workbook.SaveAs("$loglocation\errors_$server.xls",1)
$Workbook.Saved = $true
$xl.Quit()
With the PSExcel Module you can use Export-XLSX which makes this process very simple:
$loglocation = "C:\folder"
Get-ChildItem -Path $loglocation -Filter *.csv | foreach {
Export-XLSX -InputObject $_ -Path "$loglocation\$($_.BaseName).xlsx"
}
Try this, should work:
$filePath = Get-ChildItem -Path "path to csv" -filter *.csv
foreach ($file in $filePath )
{
$filename = $file.FullName
$filename
$xl = new-object -comobject excel.application
$xl.visible = $true
$Workbook = $xl.workbooks.open($filename)
$Worksheets = $Workbooks.worksheets
$Workbook.SaveAs($filename.Substring(0,$filename.Length-4) + ".xlsx",1)
$Workbook.Saved = $True
$xl.Quit()
}
Can move the excel connections outside the loop as well if you need to speed it up
Okay, I have 6 CSVs each containing one column. For this example, the data from the first CSV is used to create the initial document, and each after that is attempting to save to the document.
#ID
$csvFile = "$path\ID.csv"
$fpath = $Filename
$processes = Import-Csv -Path $csvFile
$Excel = New-Object -ComObject excel.application
$Excel.visible = $false
$workbook = $Excel.workbooks.add()
$excel.cells.item(1,1) = "ID"
$i = 2
foreach($process in $processes)
{
$excel.cells.item($i,1) = $process.ID
$i++
} #end foreach process
$workbook.saveas($fpath)
$Excel.Quit()
Remove-Variable -Name excel
[gc]::collect()
[gc]::WaitForPendingFinalizers()
#SRP
$csvFile = "$path\SRP.csv"
$processes = Import-Csv -Path $csvFile
$Excel = New-Object -ComObject excel.application
$Excel.visible = $false
$workbook = $Excel.workbooks.add()
$excel.cells.item(1,2) = "SRP"
$i = 2
foreach($process in $processes)
{
$excel.cells.item($i,2) = $process.SRP
$i++
} #end foreach process
$workbook.save($fpath)
$Excel.Quit()
Remove-Variable -Name excel
[gc]::collect()
[gc]::WaitForPendingFinalizers()
When I get down to the save line on the second one (#SRP section) I get the following error:
Cannot find an overload for "Save" and the argument count: "1".
At D:\UserAdminScripts\0_Powershell_Test_Scripts\Scripts_For_Lisa\NED Stuff\NED_Reports.ps1:130 char:15
+ $workbook.save <<<< ($fpath)
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
If I let the script run after the error, I get the same result for each column.
I'm aware this script isn't clean, because I don't need to close and reopen Excel for each run, but I butchered this script from http://blogs.technet.com/b/heyscriptingguy/archive/2010/09/09/copy-csv-columns-to-an-excel-spreadsheet-by-using-powershell.aspx. The saveas works correctly in the first attempt, it's just the save causing an error.
I find when I break the script, I get a pop-up to confirm whether or not I want to make changes to Book2.xlsx. I tell it yes and I check and Book2 has the 2nd column filled out with the data I wanted as the second column on my original sheet.
Any help is appreciated.
Have a look at the $Workbook object with Get-Member:
Save Method void Save ()
The Save() method doesn't accept any arguments and it's failing to work out what to do with that method and your file name argument.
You Quit the Excel app in the #ID section (thus closing the file) so you need to reopen the file before accessing the workbook and trying to write to it.
Well, it looks like I answered my own question, due to ConanW's inspiration to actually, you know, think critically. The answer to my issue is as follows:
#ID
$csvFile = "$path\ID.csv"
$fpath = $Filename
$processes = Import-Csv -Path $csvFile
$Excel = New-Object -ComObject excel.application
$Excel.visible = $false
$workbook = $Excel.workbooks.add()
$excel.cells.item(1,1) = "ID"
$i = 2
foreach($process in $processes)
{
$excel.cells.item($i,1) = $process.ID
$i++
} #end foreach process
#SRP
$csvFile = "$path\SRP.csv"
$processes = Import-Csv -Path $csvFile
$excel.cells.item(1,2) = "SRP"
$i = 2
foreach($process in $processes)
{
$excel.cells.item($i,2) = $process.SRP
$i++
} #end foreach process
$workbook.saveas($fpath)
$Excel.Quit()
Remove-Variable -Name excel
[gc]::collect()
[gc]::WaitForPendingFinalizers()
I'm trying to write a powershell script that will loop through each excel file in the given directory, check the file for a specifically named worksheet, and then copy that file to another location if it's a match.
Please see below for what I've already tried:
[void][reflection.assembly]::Loadwithpartialname("microsoft.office.excel")
$Excel = New-Object -ComObject Excel.Application
$tempLocation = "C:\Test\" # Path to read files
$files = Get-ChildItem C:\Test
ForEach ($file in $files)
{
#Check for Worksheet named TestSheet
$WorkBook = $Excel.Workbooks.Open($file)
$WorkSheets = $WorkBook.WorkSheets
foreach ($WorkSheet in $Workbook.Worksheets) {
If ($WorkSheet.Name -eq "TestSheet")
{$path = $tempLocation + "\" + $file
Write "Saving $path"
Copy-Item c:\Test\$file c:\Confirmed}
Else {Write "$path does not contain TestSheet"}
$WorkBook.Close()
}
}
This script returns no errors in PowerShell, but just sits there without writing anything or copying any files. Any ideas?
EDIT: Here's my final script that is now running successfully
$ErrorActionPreference= 'silentlycontinue'
$tempLocation = "C:\Source" # Path to read files
$targetlocation = "C:\Target"
Write "Loading Files..."
$files = Get-ChildItem C:\Source
Write "Files Loaded."
ForEach ($file in $files)
{
#Check for Worksheet named TestSheet
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $false
$Excel.DisplayAlerts = $false
$WorkBook = $Excel.Workbooks.Open($file.Fullname)
$WorkSheets = $WorkBook.WorkSheets | where {$_.name -eq "TestSheet"}
if($WorkSheets) {
$path = $tempLocation + "\" + $file
$dest = $targetlocation + "\" + $file
Write "Saving $path"
$WorkBook.SaveAs($dest)
}
$Excel.Quit()
Stop-Process -processname EXCEL
}
Read-host -prompt "The Scan has completed. Press ENTER to close..."
clear-host;
There were several issues with my script's logic. The following script ran successfully! It took hours of research...
$ErrorActionPreference= 'silentlycontinue'
$tempLocation = "C:\Source" # Path to read files
$targetlocation = "C:\Target"
Write "Loading Files..."
$files = Get-ChildItem C:\Source
Write "Files Loaded."
ForEach ($file in $files)
{
#Check for Worksheet named TestSheet
$Excel = New-Object -ComObject Excel.Application
$Excel.visible = $false
$Excel.DisplayAlerts = $false
$WorkBook = $Excel.Workbooks.Open($file.Fullname)
$WorkSheets = $WorkBook.WorkSheets | where {$_.name -eq "TestSheet"}
if($WorkSheets) {
$path = $tempLocation + "\" + $file
$dest = $targetlocation + "\" + $file
Write "Saving $path"
$WorkBook.SaveAs($dest)
}
$Excel.Quit()
Stop-Process -processname EXCEL
}
Read-host -prompt "The Scan has completed. Press ENTER to close..."
clear-host;
You don't need this line:
[void][reflection.assembly]::Loadwithpartialname("microsoft.office.excel")
($Excel = New-Object -ComObject Excel.Application is sufficient here)
I don't think you're referencing the full path to your Excel files. Try modifying this line:
$WorkBook = $Excel.Workbooks.Open($file)
Amend to:
$WorkBook = $Excel.Workbooks.Open($file.Fullname)
Additionally, consider adding a filter to your Get-ChildItem command, if there are sub-directories or non-Excel files, they will cause errors:
$files = Get-ChildItem C:\Test -filter "*.xls"