Excel spawned from powershell script not quitting [duplicate] - excel

This question already has answers here:
Excel, save and close after run
(6 answers)
Closed 4 years ago.
We're migrating databases so I'm using powershell to modify the hundreds of excel files that reference to old DB instance to the new one. This is all fine and works as intended. The problem I have is that the Excel application will not exit when I'm done. The process will just hang around as a background process and I need to go into task manager to kill it. Not a huge deal but it is annoying. Here is my script. I'm on powershell v5 and Office 2016.
param(
[string]$search_root=$(throw "missing search root parameter"),
[boolean]$test=$true
)
echo $search_root
$NAMEPOSTFIX = '-Updated'
$OLDCONN = 'Data Source=abc;'
$NEWCONN = 'Data Source=xyz;'
$filelist = Get-ChildItem -Path $search_root *.xls* -Recurse -Exclude '*Updated.*'
$Excel = New-Object -Com Excel.Application
$Excel.DisplayAlerts = $False
function update_con_xls{
param($file)
$Workbook = $Excel.Workbooks.Open($file)
foreach($con in $Workbook.Connections){
if ($con.OLEDBConnection -ne $null){
$con.OLEDBConnection.Connection = $con.OLEDBConnection.Connection.Replace($OLDCONN,$NEWCONN)
}
if ($con.ODBCConnection -ne $null){
$con.ODBCConnection.Connection = $con.ODBCConnection.Connection.Replace($OLDCONN,$NEWCONN)
}
}
$Workbook.Save()
$Workbook.saved = $true
$Excel.Workbooks.Close()
}
foreach ($file in $filelist) {
echo $file
if ($file.Extension -eq '.xls' -or $file.Extension -eq '.xlsx') {
$newfile = ($file.DirectoryName + '\' + $file.BaseName + $NAMEPOSTFIX + $file.Extension)
if($test){
echo $test
$newfile = ('C:\test\' + $file.Name) #for test run to copy coppy locally
}
Copy-Item $file.FullName -Destination $newfile
update_con_xls($newfile)
}
}
$Excel.Quit()
$Excel = $null

Maybe change:
$Excel.Workbooks.Close()
to :
$Excel= New-Object -ComObject Excel.Application;
$Workbook = $Excel.Workbooks.Open($file);
$Workbook.Save();
$Workbook.Close(); # <--- try
$Excel.Quit();
Remove-Variable -Name Excel;
I do not see anything here that could close your ODB and OLEDb connection trying to add:
$con.OLEDBConnection.Connection.Close();
$con.ODBCConnection.Connection.Close();
or
$con.OLEDBConnection.Close();
$con.ODBCConnection.Close();
after your work is finished.

Related

Powershell, close Excel

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

Rename multiple worksheet and multiple files with powershell

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"

timeout to powershell script

I've a script in Powershell that update Excel files with connections to SQL databases. The script works fine but the problem is if one connection doesn't work the script can't continue. Is there a way to put like a timeout or something so that can continue after a wile?
Here is the script that I have:
$libraryPath = "C:\temp\Excel\"
$excel = new-object -comobject Excel.Application
# Give delay to open
Start-Sleep -s 5
$allExcelfiles = Get-ChildItem $libraryPath -recurse -include “*.xlsx”
foreach ($file in $allExcelfiles)
{
$workbookpath = $file.fullname
Write-Host "Updating " $workbookpath
# Open the Excel file
$excelworkbook = $excel.workbooks.Open($workbookpath)
$connections = $excelworkbook.Connections
foreach ($c in $connections)
{
if ($c.DataFeedConnection -ne $null)
{
$conn = $c.DataFeedConnection.Connection
# Use regex to search and replace part of connection string
$new = $conn -replace 'ProjectName eq ''(.*)''', "ProjectName eq '$title'"
$c.DataFeedConnection.Connection = $new
Write-Host "Connection replaced."
}
}
Start-Sleep -s 5
# This will Refresh All the pivot tables data.
$excelworkbook.RefreshAll()
# The following script lines will Save the file.
Start-Sleep -s 50
$excelworkbook.Save()
$excelworkbook.Close()
}
$excel.quit()
Thanks
You could run the your script as a job, and if the job exceeds a given period terminate it.
$timeout_in_sec = 10
$excel_update = {
$libraryPath = "C:\temp\Excel\"
$excel = new-object -comobject Excel.Application
# Give delay to open
Start-Sleep -s 5
$allExcelfiles = Get-ChildItem $libraryPath -recurse -include “*.xlsx”
foreach ($file in $allExcelfiles)
{
$workbookpath = $file.fullname
Write-Host "Updating " $workbookpath
# Open the Excel file
$excelworkbook = $excel.workbooks.Open($workbookpath)
$connections = $excelworkbook.Connections
foreach ($c in $connections)
{
if ($c.DataFeedConnection -ne $null)
{
$conn = $c.DataFeedConnection.Connection
# Use regex to search and replace part of connection string
$new = $conn -replace 'ProjectName eq ''(.*)''', "ProjectName eq '$title'"
$c.DataFeedConnection.Connection = $new
Write-Host "Connection replaced."
}
}
Start-Sleep -s 5
# This will Refresh All the pivot tables data.
$excelworkbook.RefreshAll()
# The following script lines will Save the file.
Start-Sleep -s 50
$excelworkbook.Save()
$excelworkbook.Close()
}
$excel.quit()
}
$job = Start-Job -Name 'thing' -ScriptBlock $excel_update
Wait-Job -Timeout $timeout_in_sec -Job $job
if ($job.State -eq 'Running') { Stop-Job -Job $job }
Remove-Job -Job $job
Change $timeout_in_sec to whatever suites your needs.

Powershell script stops working when ran through task scheduler

I am running the script below as a scheduled task with the user logged on the server. It converts an xls file to csv using the Excel.Application COM object. The conversion works, but eventually breaks and I don't know why.
I have the task run the following command which should in theory allow it to run constantly:
powershell.exe -noexit -file "filename.ps1"
Any thoughts on what to try?
$server = "\\server"
$xls = "\path\XLS\"
$csv = "\path\CSV\"
$folder = $server + $xls
$destination = $server + $csv
$filter = "*.xls" # <-- set this according to your requirements
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{
IncludeSubdirectories = $true # <-- set this according to your requirements
NotifyFilter = [IO.NotifyFilters]"FileName, LastWrite"
}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$excelFile = $folder + $name
$E = New-Object -ComObject Excel.Application
$E.Visible = $false
$E.DisplayAlerts = $false
$wb = $E.Workbooks.Open($excelFile)
foreach ($ws in $wb.Worksheets) {
$n = "output_" + $name -replace ".XLS"
$ws.SaveAs($destination + $n + ".csv", 6)
}
$E.Quit()
}
I was doing something similar with word. I couldnt use Quit alone. I think using Quit hides Excel. Try releasing the com object by using:
$E.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($E)
Remove-Variable E
i dont know if you are opening the Excel application but if you are you can maybe use
$wb.close($false)
Let me know if it works...
Ref: https://technet.microsoft.com/en-us/library/ff730962.aspx?f=255&MSPPError=-2147217396

Using Powershell to loop through Excel files and check if Spreadsheet name exists

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"

Resources