Killing EXCEL.exe Process from C# in a Windows Service
Asked Answered
B

13

9

I have a windows service that opens up an Excel spreadsheet via the Microsoft.Office.Interop.Excel.Application object.

Application xlApp = new Application();
Workbook workbook = xlApp.Workbooks.Open(fileName, 2, false);
...
...
workbook.Close();
xlApp.Quit();

I would like to kill the EXCEL.exe process that is left running after it is done working with the workbook.

I've tried the following with no success...

// This returns a processId of 0
IntPtr processId;
GetWindowThreadProcessId(new IntPtr(xlApp.Hwnd), out processId);
Process p = Process.GetProcessById(processId.ToInt32());   
p.Kill();

Anyone have any ideas as to how I can do this via a Windows Service?

Backward answered 11/4, 2011 at 21:4 Comment(1)
I looked into doing this on an internal website and gave up for this reason.Daisey
B
6

After much reading and frustration I've found a solution!

All credit goes to dotNetkow, nightcoder and Mike Rosenblum for their solutions on this post: How do I properly clean up Excel interop objects?

Here is what I did...
1. Changed build mode of the project to "Release" (in DEBUG mode, COM objects have a hard time disposing of their references.
2. Removed all double dot expressions (all COM objects should be tied to a variable so they can be released)
3. Calling GC.Collect(), GC.WaitForPendingFinalizers(), and Marshal.FinalReleaseComObject() explicitly in a finally block

Here is the acutal code I am using:

Application xlApp = null;
Workbooks workbooks = null;
Workbook workbook = null;
Worksheet sheet = null;
Range r = null;
object obj = null;

try
{
    xlApp = new Application();
    xlApp.DisplayAlerts = false;
    xlApp.AskToUpdateLinks = false;
    workbooks = xlApp.Workbooks;
    workbook = workbooks.Open(fileName, 2, false);
    sheet = workbook.Worksheets[1];

    r = sheet.get_Range("F19");
    obj = r.get_Value(XlRangeValueDataType.xlRangeValueDefault);
}
finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (value != null) Marshal.FinalReleaseComObject(value);
    if (r != null) Marshal.FinalReleaseComObject(r);
    if (sheet != null) Marshal.FinalReleaseComObject(sheet);
    if (workbooks != null) Marshal.FinalReleaseComObject(workbooks);
    if (workbook != null)
    {
        workbook.Close(Type.Missing, Type.Missing, Type.Missing);
        Marshal.FinalReleaseComObject(workbook);
    }
    if (xlApp != null)
    {
        xlApp.Quit();
        Marshal.FinalReleaseComObject(xlApp);
    }
}
Backward answered 12/4, 2011 at 15:34 Comment(0)
S
11

Properly closing the open Excel workbook and quitting the app is extremely difficult. If I can find the links I'll post them, but essentially you must clean up all references to any COM object that you create. This includes everything from ODBCConnections (data connections), Worksheets, Workbooks, and the Excel application. A combination I got to work involved garbage collection and the System.Runtime.InteropServices.Marshal object:

// Garbage collecting
GC.Collect();
GC.WaitForPendingFinalizers();
// Clean up references to all COM objects
// As per above, you're just using a Workbook and Excel Application instance, so release them:
workbook.Close(false, Missing.Value, Missing.Value);
xlApp.Quit();
Marshal.FinalReleaseComObject(workbook);
Marshal.FinalReleaseComObject(xlApp);

Like you mentioned, looping through and killing each Excel process is usually not a good idea, since if you're running this as a Windows app you may close Excel on your user, or in a service also close an instance of Excel that is running via some other program.

Edit: See this question for more info.

Secundas answered 12/4, 2011 at 13:52 Comment(1)
Thanks for pointing me in the right direction. I used your code along with some other tips from that post and the Excel process is now closing correctly.Backward
D
8

You need to check file handles and get PID, that are opened by process and then kill it. It worked for me.

private void genExcel(
{
   int pid = -1;
   //Get PID
   xlApp = new Excel.Application();
   HandleRef hwnd = new HandleRef(xlApp, (IntPtr)xlApp.Hwnd);
   GetWindowThreadProcessId(hwnd, out pid);
   .
   .
   .
   .
   //Finally
   KillProcess(pid,"EXCEL");
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowThreadProcessId(HandleRef handle, out int processId);

private void KillProcess(int pid, string processName)
{
    // to kill current process of excel
    System.Diagnostics.Process[] AllProcesses = System.Diagnostics.Process.GetProcessesByName(processName);
    foreach (System.Diagnostics.Process process in AllProcesses)
    {
       if (process.Id == pid)
       {
         process.Kill();
       }
    }
    AllProcesses = null;
}
Doom answered 6/10, 2012 at 9:20 Comment(1)
Your answer deserves it weight in gold!!! I have been trying to work this out for months!!!Termination
B
6

After much reading and frustration I've found a solution!

All credit goes to dotNetkow, nightcoder and Mike Rosenblum for their solutions on this post: How do I properly clean up Excel interop objects?

Here is what I did...
1. Changed build mode of the project to "Release" (in DEBUG mode, COM objects have a hard time disposing of their references.
2. Removed all double dot expressions (all COM objects should be tied to a variable so they can be released)
3. Calling GC.Collect(), GC.WaitForPendingFinalizers(), and Marshal.FinalReleaseComObject() explicitly in a finally block

Here is the acutal code I am using:

Application xlApp = null;
Workbooks workbooks = null;
Workbook workbook = null;
Worksheet sheet = null;
Range r = null;
object obj = null;

try
{
    xlApp = new Application();
    xlApp.DisplayAlerts = false;
    xlApp.AskToUpdateLinks = false;
    workbooks = xlApp.Workbooks;
    workbook = workbooks.Open(fileName, 2, false);
    sheet = workbook.Worksheets[1];

    r = sheet.get_Range("F19");
    obj = r.get_Value(XlRangeValueDataType.xlRangeValueDefault);
}
finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (value != null) Marshal.FinalReleaseComObject(value);
    if (r != null) Marshal.FinalReleaseComObject(r);
    if (sheet != null) Marshal.FinalReleaseComObject(sheet);
    if (workbooks != null) Marshal.FinalReleaseComObject(workbooks);
    if (workbook != null)
    {
        workbook.Close(Type.Missing, Type.Missing, Type.Missing);
        Marshal.FinalReleaseComObject(workbook);
    }
    if (xlApp != null)
    {
        xlApp.Quit();
        Marshal.FinalReleaseComObject(xlApp);
    }
}
Backward answered 12/4, 2011 at 15:34 Comment(0)
S
3

I don't know if my answer is not what you're searching for... if so tell me and i gonna delete it. Anyway I used this:

Application xlApp = new Application();
xlApp.DisplayAlerts = false;
xlApp.Visible = true; // Only for debug purposes
Workbook workbook = xlApp.Workbooks.Open(filename, 2, false);
...
...
workbook.Close();
xlApp.Quit();

Closing workbook and quitting xlApp removes EXCEL.EXE from memory in my pc.
I'm using Windows XP 32bit and Microsoft Office 2007.

I've also tried to open another excel file before working with this test-app: a second EXCEL.EXE gets opened and (using Quit) closed at the end, leaving the first instance untouched.

Selfpollination answered 11/4, 2011 at 21:20 Comment(3)
It's usually a good idea to also set Application.DisplayAlerts = False to prevent (most) blocking dialogs from appearing.Olwen
I gave this a try and it didn't kill the Excel instance. The service is running on Windows Server 2008 with Office 2010.Backward
@thiag0: I saw that Excel.exe was often closed not right after quitting, but after exiting the method I called Quit() in. Just to be sure I used GC.Collect() too, but it could be expensive...Selfpollination
S
3

I have used a simple but effective solution

finally   { 
GC.Collect();
GC.WaitForPendingFinalizers();           
        if (xlApp != null)
            {
                xlApp .Quit();
                int hWnd = xlApp .Application.Hwnd;
                uint processID;GetWindowThreadProcessId((IntPtr)hWnd, out processID);
                Process[] procs = Process.GetProcessesByName("EXCEL");
                foreach (Process p in procs)
                {
                    if (p.Id == processID)
                        p.Kill();
                }
                Marshal.FinalReleaseComObject(xlApp );
            } 
        }

Find all Excell.exe processes . then get the process ID of my excelApplication . kill only the process whose id match. Use to declare GetWindowThreadProcessId in the class :

[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
Spinule answered 19/11, 2011 at 9:39 Comment(4)
Have you read the accepted answer? Your solution may close excel on the user wich us not good. The accepted solution also works, and avoids this problem. How do you think this answers contributes?Heavyhanded
@Heavyhanded For me personally this answer contributed the only solution that worked. I have an extremely basic application that automates Access 2000 on Windows Server 2008R2 64bit. I have ensured that I follow all possible other recommended guidelines, but my processes just do not close. Using this approach, I can use the PID to keep closing the instances I need to.Habitue
@Safrin, wish I could give you more than +1. Thank you much for taking the time. :) I was going absolutely nuts with this damn Automation.Habitue
The solution is wrong. Excel process will stay alive up until calling Marshal.FinalReleaseComObject(xlApp);. Because of this, you are always killing it by force even before it is gracefully released. I suggest modifying your answer and switching the lines of code. Also, from my own experience, although you release everything the way you should, macro code which you run through interop might leave something behind and this will block your application from exiting. It's good to have this backup system if you can apply it to your specific usage of Excel (having in mind other Excel users).Pasargadae
H
2

My solution

[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

private void GenerateExcel()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Process Id
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    excel.Quit();

    // Kill him !
    excelProcess.Kill();
}
Hachure answered 18/6, 2014 at 9:22 Comment(1)
"user32.dll" its a dll name if yes then from where i get this ?Farah
B
1

Below is the code which open and deletes the Excel instance. We just need to make sure all objects related to Excel are closed.

    string strFilePath = @"C:\Sample.xlsx";
        try
        {
            Excel.Application excelApp = null;
        Excel.Workbook excelWorkbook = null;
        Excel.Sheets excelSheets = null;
        Excel.Worksheet excelWorksheet = null;
        Excel.Workbooks excelWorkbooks = null;
        Excel.Range excelUsedRange = null;



            excelApp = new Microsoft.Office.Interop.Excel.Application();
            int nData = excelApp.Hwnd;
            // excelApp = new Excel.ApplicationClass();
            //excelApp.Visible = true;
            excelWorkbooks = excelApp.Workbooks;
            excelWorkbook = excelWorkbooks.Add(System.Reflection.Missing.Value);

            excelWorkbook = excelApp.Workbooks.Open(strFilePath, 2, false);
            //excelWorkbook = excelApp.Workbooks.Open(strFilePath,
            //                                                                                       
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing);


            excelSheets = excelWorkbook.Worksheets;
           // excelWorksheet = (Excel.Worksheet)excelSheets.get_Item(1);
            excelWorksheet = (Excel.Worksheet)excelWorkbook.Worksheets["Dem0_1"];



            excelUsedRange = excelWorksheet.UsedRange;


            //Excel.Range lastCell = usedRange.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
            //int lastRow = lastCell.Row;
            //int lastCol = lastCell.Column;
            //int rowMin = lastRow + 1;
            //int colMin = lastCol + 1;

            int nRowsCount = excelUsedRange.Rows.Count;
            int nColCount = excelUsedRange.Columns.Count;



             int N_Quality_Header = -1;
             int N_Measurement_Name = -1;
             int N_Lower_Tolerance = -1;
             int N_Upper_Tolerance = -1;


             //Read the Columns Index 
             for (int nColIndex = 1; nColIndex <= nColCount; nColIndex++)
             {
                 Excel.Range cell = usedRange.Cells[1, nColIndex] as Excel.Range;
                 String strCellValue = cell.Value2.ToString();
                 if (strCellValue == "Quality Header")
                     N_Quality_Header = nColIndex;

                 else if (strCellValue.IndexOf("Measurement Name", StringComparison.OrdinalIgnoreCase) > -1)
                     N_Measurement_Name = nColIndex;
                 else if (strCellValue.IndexOf("Lower Tolerance", StringComparison.OrdinalIgnoreCase) > -1)
                     N_Lower_Tolerance = nColIndex;
                 else if (strCellValue.IndexOf("Upper Tolerance", StringComparison.OrdinalIgnoreCase) > -1)
                     N_Upper_Tolerance = nColIndex;
             }

             //Read all rows to get the values
             for (int nRowIndex = 2; nRowIndex <= nRowsCount; nRowIndex++)
             {
                 Excel.Range cellQualityHeader = usedRange.Cells[nRowIndex, N_Quality_Header] as Excel.Range;
                 String strValue = cellQualityHeader.Value2.ToString();
                 if (strValue == String_Empty)
                     continue;


             }


        }
        catch (Exception oException)
        {


        }
        finally
        {
            excelUsedRange.Clear();
            //excelWorkbook.Save();
            excelWorkbook.Close(false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);

            excelWorkbooks.Close();
            excelApp.Quit();

            Marshal.ReleaseComObject(excelUsedRange);
            Marshal.ReleaseComObject(excelWorksheet);
            Marshal.ReleaseComObject(excelSheets);
            Marshal.ReleaseComObject(excelWorkbooks);
            Marshal.ReleaseComObject(excelWorkbook);
            Marshal.ReleaseComObject(excelApp);


            excelUsedRange = null;
            excelWorksheet = null;
            excelSheets = null;
            excelWorkbooks = null;
            excelWorkbook = null;
            excelApp = null;

            GC.GetTotalMemory(false);
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.GetTotalMemory(true);



        }
Birdcage answered 27/9, 2012 at 7:8 Comment(0)
T
0

I would use Process.GetProcess and look for the exe there I wouldn't trust anything having to do with the window in the service world as I think the windows get created on a deskstation you don't have access to.

Tatyanatau answered 11/4, 2011 at 21:12 Comment(1)
Thanks for your response. I am able to kill it via a foreach loop with Process.GetProcessByName("EXCEL") but this windows service is multithreaded so I have to kill the correct instance.Backward
T
0

I am using:

Process[] AllProcesses = Process.GetProcessesByName("EXCEL.EXE");

to have the process killed.

Tsai answered 8/8, 2014 at 3:49 Comment(0)
K
0

Maybe it's not so very elegant, but I ended up with a combination of the accepted solution and the one of Safrin. So first I try to do it the elegant way and if it fails I use brute force. The reason is that the code is part of a batch procedure which must be able to continue even when one Excel refresh operation failes. My problem was that some failures had to do with faults in the PowerPivot model which brought up a dialog with an error message. This dialog was not visible because it runs as background process and it seemed that Excel wouldn't close, and my process wouldn't continue, untill the dialog was closed (?!). So starting the proces in a separate thread with an time out mechanism and kill Excel on disposale of my work object if quiting doesn't work was the only solution I could think of (that works)...

    public void Dispose()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        if (workbook != null)
        {
            try
            {
                workbook.Close(false);
                Marshal.FinalReleaseComObject(workbook);
            }
            catch { }
        }
        if (excel != null)
        {
            try { excel.Quit(); }
            catch {
                int hWnd = excel.Application.Hwnd;
                uint processID; 
                GetWindowThreadProcessId((IntPtr)hWnd, out processID);
                Process[] procs = Process.GetProcessesByName("EXCEL");
                foreach (Process p in procs)
                {
                    if (p.Id == processID) p.Kill();
                }

            }
            Marshal.FinalReleaseComObject(excel);
        }
    }
Kendra answered 31/10, 2014 at 16:42 Comment(0)
H
0

This is my code to kill all unused Excel Process

Process[] process = Process.GetProcessesByName("excel");
        foreach (Process excel in process)
        {
            if (excel.HasExited)
            {
                excel.Kill();
            }
        }
        process = null;
Hermitage answered 16/3, 2016 at 5:45 Comment(0)
M
0

This is what I'm doing.. Over kill perhaps.. but works well for me.

    [DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

    private void KillProcess(uint pid, string processName) {
        // to kill current process of excel
        System.Diagnostics.Process[] AllProcesses = System.Diagnostics.Process.GetProcessesByName(processName);
        foreach (System.Diagnostics.Process process in AllProcesses) {
            if (process.Id == pid) {
                process.Kill();
            }
        }
        AllProcesses = null;
    }
    public void ReleaseObject(object obj) {
        try {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
            obj = null;
        }
        catch (Exception ex) {
            obj = null;
            MessageBox.Show("Exception Occured while releasing object " + ex.ToString());
        }
        finally { GC.Collect(); }
    }


                        xlWorkBook.Save();
                        xlWorkBook.Close();
                        xlApp.Quit();

                        uint pid;
                        HandleRef hwnd = new HandleRef(xlApp, (IntPtr)xlApp.Hwnd);
                        GetWindowThreadProcessId((IntPtr)xlApp.Hwnd, out pid);
                        //GetWindowThreadProcessId(hwnd, out pid);

                        KillProcess(pid, "EXCEL");

                        ReleaseObject(worksheets);
                        ReleaseObject(xlWorkBook);
                        ReleaseObject(xlApp);
Mia answered 1/4, 2020 at 12:41 Comment(0)
V
0

To kill exact Excel process used for your app at that moment firstly identify its PID by entering the below code under the saving and closing instructions in your method 1 (potentially main method):

    int pid = -1;
    HandleRef hwnd = new HandleRef(xlApp, (IntPtr)xlApp.Hwnd);
    GetWindowThreadProcessId(hwnd, out pid);

    KillProcess(pid, "EXCEL");

Additionally below the above method 1 enter this new method:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowThreadProcessId(HandleRef handle, out int processId);
static public void KillProcess(int pid, string processName)
{
    // to kill current process of excel
    Process[] AllProcesses = Process.GetProcessesByName(processName);
    foreach (Process process in AllProcesses)
    {
        if (process.Id == pid)
        {
            process.Kill();
        }
    }
    AllProcesses = null;
}

So all code is going to be like that in larger area view:

public CopyPaste2()
{
    srcWb= @"C:\WIP\sourceWB.xlsm";
    destWb= @"C:\WIP\destinationWB.xlsm";

    Application xlApp = new Application();
    xlApp.Visible = true;

    Workbook strSrcWb= xlApp.Workbooks.Open(srcWb, 0, false, 5, "", "", true, XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);
    Workbook strDestWb= xlApp.Workbooks.Open(destWb, 0, false, 5, "", "", true, XlPlatform.xlWindows, "\t", false, false, 0, true, 1, 0);

    Worksheet srcWs = strSrcWb.Worksheets.get_Item("Sheet1");
    Worksheet destWs = strDestWb.Worksheets.get_Item("Sheet1");

    ... rest of the executive methods ...

    strDestWb.Save();
    strSrcWb.Close();
    strDestWb.Close();
    xlApp.Quit();

    int pid = -1;
    HandleRef hwnd = new HandleRef(xlApp, (IntPtr)xlApp.Hwnd);
    GetWindowThreadProcessId(hwnd, out pid);

    KillProcess(pid, "EXCEL");
}


[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowThreadProcessId(HandleRef handle, out int processId);
static public void KillProcess(int pid, string processName)
{
    // to kill current process of excel
    Process[] AllProcesses = Process.GetProcessesByName(processName);
    foreach (Process process in AllProcesses)
    {
        if (process.Id == pid)
        {
            process.Kill();
        }
    }
    AllProcesses = null;
}
Vengeance answered 6/9, 2021 at 9:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.