thiag0
thiag0

Reputation: 2229

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

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?

Upvotes: 9

Views: 37665

Answers (13)

ZIELIK
ZIELIK

Reputation: 21

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;
}

Upvotes: 0

Ichi San
Ichi San

Reputation: 23

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);

Upvotes: 0

E Coder
E Coder

Reputation: 295

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;

Upvotes: 0

dotNetkow
dotNetkow

Reputation: 5313

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.

Upvotes: 11

Ben
Ben

Reputation: 1

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);
        }
    }

Upvotes: 0

Marc Zeroc
Marc Zeroc

Reputation: 19

I am using:

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

to have the process killed.

Upvotes: 0

Loart
Loart

Reputation: 71

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();
}

Upvotes: 2

Dorn
Dorn

Reputation: 81

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;
}

Upvotes: 8

Mahadev HK
Mahadev HK

Reputation: 11

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);



        }

Upvotes: 1

Safrin
Safrin

Reputation: 31

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);

Upvotes: 3

thiag0
thiag0

Reputation: 2229

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);
    }
}

Upvotes: 6

Marco
Marco

Reputation: 57573

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.

Upvotes: 3

rerun
rerun

Reputation: 25495

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.

Upvotes: 0

Related Questions