sacabuche
sacabuche

Reputation: 2849

Can't close Excel completely using win32com on Python

This is my code, and I found many answers for VBA, .NET framework and is pretty strange. When I execute this, Excel closes.

from win32com.client import DispatchEx
excel = DispatchEx('Excel.Application')
wbs = excel.Workbooks
wbs.Close()
excel.Quit()
wbs = None
excel = None # <-- Excel Closes here

But when I do the following, it does not close.

excel = DispatchEx('Excel.Application')
wbs = excel.Workbooks
wb = wbs.Open('D:\\Xaguar\\A1.xlsm')
wb.Close(False)
wbs.Close()
excel.Quit()
wb = None
wbs = None
excel = None  # <-- NOT Closing !!!

I found some possible answer in Stack Overflow question Excel process remains open after interop; traditional method not working. The problem is that is not Python, and I don't find Marshal.ReleaseComObject and GC. I looked over all the demos on ...site-packages/win32com and others.

Even it does not bother me if I can just get the PID and kill it.

I found a workaround in Kill process based on window name (win32).

May be not the proper way, but a workround is:

def close_excel_by_force(excel):
    import win32process
    import win32gui
    import win32api
    import win32con

    # Get the window's process id's
    hwnd = excel.Hwnd
    t, p = win32process.GetWindowThreadProcessId(hwnd)
    # Ask window nicely to close
    win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
    # Allow some time for app to close
    time.sleep(10)
    # If the application didn't close, force close
    try:
        handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, p)
        if handle:
            win32api.TerminateProcess(handle, 0)
            win32api.CloseHandle(handle)
    except:
        pass

excel = DispatchEx('Excel.Application')
wbs = excel.Workbooks
wb = wbs.Open('D:\\Xaguar\\A1.xlsm')
wb.Close(False)
wbs.Close()
excel.Quit()
wb = None
wbs = None
close_excel_by_force(excel) # <--- YOU #@#$# DIEEEEE!! DIEEEE!!!

Upvotes: 31

Views: 42290

Answers (10)

Soheil Kh
Soheil Kh

Reputation: 21

In my approach, I obtain the Process ID (PID) immediately upon creating an instance. This PID serves as a crucial identifier, enabling me to terminate the process efficiently once I've completed my tasks. Instead of relying on the conventional Excel.Quit method, I implement a custom function that accepts the PID as input:

xl_app = win32.DispatchEx('Excel.Application')

_, v_pid = win32process.GetWindowThreadProcessId(xl_app.Hwnd)

Perform tasks...

close_instance_by_pid(v_pid)

The close_instance_by_pid function effectively manages the termination process:

import psutil
import logging

def close_instance_by_pid(pid):
    try:
        process = psutil.Process(pid)
        for child in process.children(recursive=True):
            child.kill()
        process.kill()
        logging.info(f"Excel instance with PID {pid} successfully closed.")
    except psutil.NoSuchProcess:
        logging.error(f"No process found with PID {pid}.")
    except Exception as e:
        logging.error(f"Error closing Excel instance with PID {pid}: {e}")

Upvotes: 2

Mian
Mian

Reputation: 161

I guess very late to the party but I have tried all answers and some other methods as well, but every method failed at some point when it comes to already opened excel files not to close.

If an excel file is already opened, it should not close, but the excel application's instance must be closed/end from the Task Manager

So this Works Perfectly fine in my case, i wish the same for everyone's case in future use

def xel_to_pdf(xel, pdf):
    excel = client.DispatchEx("Excel.Application") #Can use client.Dispatch as well but that will cause a popup alert as the file is read-only everytime you open the saved excel file. So DispatchEx will save you from this heck
    excel.Interactive = False
    excel.Visible = False
    excel.DisplayAlerts = False

    sheets = excel.Workbooks.Open(xel, None, True)
    work_sheets = sheets.Worksheets('sheet1')
    work_sheets.ExportAsFixedFormat(0, pdf) #for example you want to convert excel to pdf
    for w_b in excel.Workbooks:
        if w_b.Name == xel:
            w_b.Close()
    excel.Quit()
    # excel.Application.Quit() will not remove excel instance from the Task-Manager, So use excel.Quit()

xel_to_pdf('excel_path.xlsx', 'PATH_of_pdf_name')

You can use Try Except for any Error Handling according to your needs...

Upvotes: 0

Tyler
Tyler

Reputation: 500

After numerous failed attempts, this is the method that finally worked for me:

import pythoncom, win32com.client, psutil

# For testing purposes, choose whether the Excel process will stay open or not.
method = 'good'

# Start and stop Excel instance.
if method == 'bad':
    excel = win32com.client.gencache.EnsureDispatch('Excel.Application')
    excel.Application.Quit()
elif method == 'good':
    pythoncom.CoInitialize()
    excel = win32com.client.dynamic.Dispatch('Excel.Application')
    excel.Application.Quit()
    excel = None
    pythoncom.CoUninitialize()

# Check if Excel is still open, and if so, give the option to close it.
for proc in psutil.process_iter():
    if any(procstr in proc.name() for procstr in ['EXCEL']):
        print(
            'Excel process found. Close?\n[Y/N]: ',
            end=''
        )

        answer = input()

        if answer.lower() == 'y' or answer.lower() == 'yes':
            for proc in psutil.process_iter():
                if any(procstr in proc.name() for procstr in ['EXCEL']):
                    proc.kill()
        else:
            quit()

I haven't tested this script with Workbooks but my understanding is that you'll also need to close and set them equal to None before excel.Application.Quit() is called.

Upvotes: 0

Chris Collett
Chris Collett

Reputation: 1279

Most of the other answers leverage killing Excel entirely. Problem is, this will close any other instance of Excel that might have been opened.

To get the workbook you opened to actually close, you need to close and delete your workbook (return value from excel.Workbooks.Open).

import win32com.client

excel = win32com.client.Dispatch('Excel.Application')
workbook = excel.Workbooks.Open(r'your\excel\file.xlsm')
# Do whatever you need to with the workbook
workbook.Close(False)  # or workbook.Close(True) if you want to save it
del(workbook)

This can be wrapped up in a try ... except ... block to ensure that it gets closed even if there was an error. For example...

import win32com.client
import logging

excel = win32com.client.Dispatch('Excel.Application')
workbook = excel.Workbooks.Open(r'your\excel\file.xlsm')
try:
    some_function_to_use_workbook(workbook)
    workbook.close(True)
except Exception as error:
    logging.error(error)
    workbook.close(False)
finally:
    del(workbook)

If for whatever reason you REALLY need to kill Excel and close all open workbooks, then you'll need to force close all workbooks, quit the application and delete the excel object.

import win32com.client

excel = win32com.client.Dispatch('Excel.Application')
workbook = excel.Workbooks.Open(r'your\excel\file.xlsm')
# Do whatever you need to with the workbook
workbook.Close(False)
del(workbook)
excel.Workbooks.Close()
excel.Application.Quit()
del(excel)

Upvotes: 3

Paul
Paul

Reputation: 982

I have found that killing an Excel process through win32com is not always that reliable. These include excel.Application.Quit(), and del excel as suggested above.

I found the only way to make sure it is dead is to physically kill the EXCEL.EXE process:

import psutil

def kill_excel():
    for proc in psutil.process_iter():
        if proc.name() == "EXCEL.EXE":
            proc.kill()

Downside is that this script will obviously kill all excel process currently running. IF you can live with this, then this is not a bad option.

Upvotes: 4

Kn.Bk
Kn.Bk

Reputation: 21

To any newcomer from search engine:

In my trial, this answer says right Python should handle the lifecycle of the COM object... . Just set excel = None and the Excel.exe PID will be closed automatically now or after a while. If you still find the Excel.exe existing for a long time, call python gc collect.

import gc
gc.collect()

Upvotes: 2

LeanMan
LeanMan

Reputation: 524

Python should handle the lifecycle of the COM object. Just set excel = None. See this for reference:

# CH9 says: Python manages COM lifetimes automatically for you; when your excel 
#           variable is no longer used, Excel automatically closes. In Python,
#           the simplest way to remove this variable is to assign it to another 
#           value. If you use the following code, notice that Excel vanishes 
#           from the screen; it knows there are no longer any programs referring to it. 

src:

http://www.icodeguru.com/WebServer/Python-Programming-on-Win32/ch05.htm#:~:text=xl%20=%20None

Upvotes: 2

Robert Criqui
Robert Criqui

Reputation: 218

What worked for me was making sure to de-reference any variables that you assigned along the way like so:

import win32com.client as win32

fileDirectory = 'Hello World.xlsx'

#excelFile = win32.Dispatch('Excel.Application')
excelFile = win32.gencache.EnsureDispatch('Excel.Application')

excelFile.Visible = True
excelFile.DisplayAlerts = True

wb = excelFile.Workbooks.Open(fileDirectory)
ws = wb.Sheets("Sell")

ws.Range("D1").Value = "Hello World!"
ws = None

wb.Close(False)
wb = None

excelFile.Quit()
excelFile = None

It worked with either Dispatch format.

Upvotes: 4

ROJI
ROJI

Reputation: 151

Try this:

wbs.Close()
excel.Quit()
del excel # this line removed it from task manager in my case

Upvotes: 14

Aaron S
Aaron S

Reputation: 731

I have this in my files that use Excel:

self.excel.Application.Quit()

Upvotes: 3

Related Questions