0

I am uploading an excel file, opening it, reading the contents, closing it and removing it.

All works well, however I have noticed the process 'Microsoft Excel' still runs in the background in task manager.

Excel Process

I have tried searching around and have tried different answers but none of them work for me. Does anyone have any ideas?

I am trying different ways of closing the app, releasing the com but having no luck.

The code I am using:

    public ActionResult UploadUpdateOOBList()
    {
        CheckPermissions("UpdateOOBList");

        string[] typesallowed = new string[] { ".xls", ".xlsx" };

        HttpPostedFileBase file = Request.Files[0];
        var fname = file.FileName;

        if (!typesallowed.Any(fname.Contains))
        {
            return Json("NotAllowed");
        }

        file.SaveAs(Server.MapPath("~/Uploads/OOB List/") + fname);

        //Create COM Objects. Create a COM object for everything that is referenced
        Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
        Microsoft.Office.Interop.Excel.Workbooks xlWorkbooks = xlApp.Workbooks;
        Microsoft.Office.Interop.Excel.Workbook xlWorkbook = xlWorkbooks.Open(Server.MapPath("~/Uploads/OOB List/") + fname);
        Microsoft.Office.Interop.Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[2];
        Microsoft.Office.Interop.Excel.Range xlRange = xlWorksheet.UsedRange;

        //Create empty OOB data list
        List<OOBList.OOBDetails> oob_data = new List<OOBList.OOBDetails>();

        int rowcount = xlRange.Rows.Count;

        for (int down = 4; down <= rowcount; down++)
        {

            //Make sure first column isn't null
            if( xlRange.Cells[down, 1] != null && xlRange.Cells[down, 1].Value2 != null )
            {
                string siteno = xlRange.Cells[down, 1].Value2.ToString();
                string sitename = xlRange.Cells[down, 2].Value2.ToString();
                string description = xlRange.Cells[down, 4].Value2.ToString();
                string cabinoob = xlRange.Cells[down, 5].Value2.ToString();
                string toweroob = xlRange.Cells[down, 6].Value2.ToString();
                string manageoob = xlRange.Cells[down, 7].Value2.ToString();
                string resolutiondate = xlRange.Cells[down, 8].Value2.ToString();
                var resolutiondate_converted = DateTime.FromOADate(Convert.ToDouble(resolutiondate)).ToString("dd/MM/yyyy");

                oob_data.Add(new OOBList.OOBDetails
                {
                    SiteNo = siteno,
                    SiteName = sitename,
                    Description = description,
                    CabinOOB = cabinoob,
                    TowerOOB = toweroob,
                    ManageOOB = manageoob,
                    TargetResolutionDate = resolutiondate_converted
                });

                Debug.Write("Adding SiteNo: " + siteno);
            }

        }

        //HERE IS THE PROBLEM, DOESNT SEEM TO CLOSE THE PROCESS.
        xlWorkbook.Close();
        xlApp.Quit();

        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkbook);
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkbooks);
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorksheet);
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange);
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
        xlWorkbook = null;
        xlWorkbooks = null;
        xlWorksheet = null;
        xlRange = null;
        xlApp = null;

        GC.Collect();

        //Now delete file.
        System.IO.File.Delete(Server.MapPath("~/Uploads/OOB List/") + fname);

        var nowdate = DateTime.Now.ToString("dd/MM/yyyy");
        System.IO.File.WriteAllText(Server.MapPath("~/Uploads/OOB List/lastupdated.txt"),nowdate);

        return Json("Success");

    }
Zack Antony Bucci
  • 571
  • 1
  • 12
  • 29
  • 1
    A few years ago, I faced this zombie processes in an application. The final solution was a bit .... crazy: At program start, I enumerated all Excel processes, storing all process ID´s in a dictionary. Then, I instantiated the new Excel application. Then I enumerated the Excep processes again - the one not int he list were my "new" one. After my processing I killed that process. As I told you - very very ugly & wicked – nabuchodonossor Feb 20 '19 at 12:07
  • So if I somehow stored the XlApp Process ID in a list then loop through that list and use the Process.Kill function so it only kills that excel process instead of other excel processes too? Because people may have other excel documents open. – Zack Antony Bucci Feb 20 '19 at 12:16
  • No, at the time, when you instantiate the new process, first create a list of "not kill this pids", then create the instance, check again (the one pid NOT in the list is the one you should kill at end). – nabuchodonossor Feb 20 '19 at 12:18
  • As additional security feature: if you find two excel processes NOT in the list, then you can´t delete one, because you don´t know which is yours. But usually the user should not be "fast" enough to click a button in your app (then you create list, create instance, compare, store pid for later kill) AND STARTS excel at the very same second your process needs to .. make this list, start instance, compare new list) – nabuchodonossor Feb 20 '19 at 12:19
  • Look at: https://stackoverflow.com/a/1893653/5175942 and https://stackoverflow.com/a/25135685/5175942 and https://stackoverflow.com/questions/158706/how-do-i-properly-clean-up-excel-interop-objects?rq=1 – MacroMarc Feb 20 '19 at 12:31
  • Possible duplicate of [How do I properly clean up Excel interop objects?](https://stackoverflow.com/questions/158706/how-do-i-properly-clean-up-excel-interop-objects) – MacroMarc Feb 20 '19 at 12:32
  • Yes it is a duplicate technically but none of the answers on that question solved my problem. – Zack Antony Bucci Feb 20 '19 at 12:35
  • Are you saying the first link I gave isn't very similar to the exact answer you posted? getting the HWND property of the create Excel and then killing by matching the HWND with the MainWindowHandle property of all running processes? – MacroMarc Feb 20 '19 at 13:17

4 Answers4

0

found that somewhere: "Automation servers register themselves in the Running Object Table (ROT) through the RegisterActiveObject() API.

NOTE: If there are multiple instances of an automation server running at the same time, the GetActiveObject() API function returns the IDispatch pointer to the instance that was first running.

Theoretically, you can iterate the ROT for each individual instance, but Office applications do not register themselves if another instance is already in the ROT because the moniker for itself is always the same, and cannot be distinguished. This means that you cannot attach to any instance except for the first. However, because Office applications also register their documents in the ROT, you can successfully attach to other instances by iterating the ROT looking for a specific document, attaching to this document, and then getting the Application object from this document. For a code example of iterating the ROT and looking for a document name, click the article number below to view the article in the Microsoft Knowledge Base: 190985 How To Get IDispatch of an Excel or Word Document from an OCX"

hope this is a hint - whish I had known this back than....

nabuchodonossor
  • 2,095
  • 20
  • 18
0

The following code solved my problem upon nabuchodonossor's comment:

       //Create don't kill processes
        var dontkill = new List<Process>();
        Process[] procs = Process.GetProcessesByName("EXCEL");
        foreach (Process p in procs)
        {
            dontkill.Add(p);
        }

        //EXCEL CODE HERE.

        xlWorkbook.Close();
        xlApp.Quit();

        //Now kill only the created process above.
        procs = Process.GetProcessesByName("EXCEL");
        foreach (Process p in procs)
        {
            if( !dontkill.Contains(p))
            {
                p.Kill();
            }
        }
        Marshal.FinalReleaseComObject(xlApp);
Zack Antony Bucci
  • 571
  • 1
  • 12
  • 29
0

When I worked with the Excel Interop library, I opened and closed the document with the following code:

Application xlApp = null;
Workbook xlWorkbook = null;
_Worksheet xlWorksheet = null;
Range xlRange = null;

try
{
    xlApp = new Application();
    xlWorkbook = xlApp.Workbooks.Open(filename);
    xlWorksheet = xlWorkbook.Sheets[1];
    xlRange = xlWorksheet.UsedRange;

    // Do stuff with excel data
}
finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();

    //release com objects to fully kill excel process from running in the background
    if (xlRange != null)
    {
        Marshal.ReleaseComObject(xlRange);
    }

    if (xlWorksheet != null)
    {
        Marshal.ReleaseComObject(xlWorksheet);
    }

    //close and release
    if (xlWorkbook != null)
    {
        xlWorkbook.Close();
        Marshal.ReleaseComObject(xlWorkbook);
    }

    //quit and release
    if (xlApp != null)
    {
        xlApp.Quit();
        Marshal.ReleaseComObject(xlApp);
    }
}
Aars93
  • 379
  • 4
  • 10
0

Just another note to you guys, I decided to use ClosedXML.

It took around 30 seconds for Interop Excel to read 750 rows.

Then it took ClosedXML 3 seconds to do the same thing with the following code which also does not leave any nasty processes behind:

//Create empty OOB data list
        List<OOBList.OOBDetails> oob_data = new List<OOBList.OOBDetails>();

        string fileName = Server.MapPath("~/Uploads/OOB List/") + fname;
        using (var excelWorkbook = new XLWorkbook(fileName))
        {
            var nonEmptyDataRows = excelWorkbook.Worksheet(2).RowsUsed();

            foreach (var dataRow in nonEmptyDataRows)
            {
                //for row number check
                if (dataRow.RowNumber() >= 4 )
                {

                    string siteno = dataRow.Cell(1).GetValue<string>();
                    string sitename = dataRow.Cell(2).GetValue<string>();
                    string description = dataRow.Cell(4).GetValue<string>();
                    string cabinoob = dataRow.Cell(5).GetValue<string>();
                    string toweroob = dataRow.Cell(6).GetValue<string>();
                    string manageoob = dataRow.Cell(7).GetValue<string>();
                    string resolutiondate = dataRow.Cell(8).GetValue<string>();
                    string resolutiondate_converted = resolutiondate.Substring(resolutiondate.Length - 9);

                    oob_data.Add(new OOBList.OOBDetails
                    {
                        SiteNo = siteno,
                        SiteName = sitename,
                        Description = description,
                        CabinOOB = cabinoob,
                        TowerOOB = toweroob,
                        ManageOOB = manageoob,
                        TargetResolutionDate = resolutiondate_converted
                    });

                    Debug.Write("Adding SiteNo: " + siteno + "\n");
                }
            }
        }
Zack Antony Bucci
  • 571
  • 1
  • 12
  • 29