5

I have been struggling with this problem for a couple of days, I have made reasearch and applied all the suggestions I found on various forums but I'm still unable to solve it.

My problem is with excel using interop library, I have an excel file used as template, so I am opening it and saving in a new location with a new name. Everything works great except that the Excel process keeps runing after the file is created and closed.

This is my code

protected string CreateExcel(string strProjectID, string strFileMapPath)
{
    string strCurrentDir = HttpContext.Current.Server.MapPath("~/Reports/Templates/");
    string strFile = "Not_Created";

    Application oXL;
    Workbook oWB;        

    oXL = new Application();
    oXL.Visible = false;

    Workbooks wbks = oXL.Workbooks;
    //opening template file
    oWB = wbks.Open(strFileMapPath);        

    oXL.Visible = false;
    oXL.UserControl = false;
    strFile = strProjectID + "_" + DateTime.Now.Ticks.ToString() + ".xlsx";
    //Saving file with new name
   oWB.SaveAs(strCurrentDir + strFile, XlFileFormat.xlWorkbookDefault, null, null,    false, false, XlSaveAsAccessMode.xlExclusive, false, false, null, null);

    oWB.Close(false, strCurrentDir + strFile, Type.Missing);

    wbks.Close();

    oXL.Quit();


    System.Runtime.InteropServices.Marshal.ReleaseComObject(oXL);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(wbks);
    System.Runtime.InteropServices.Marshal.ReleaseComObject(oWB);


    oWB = null;
    oXL = null;
    wbks = null;
    GC.Collect();

    return strFile;
}

As you can see I am closing and releasing all the objects but the application does not quit.

I'm testing in a Windows Server 2008(production) and Windows 7(development) both in 32bits with IIS7.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
roma8716
  • 161
  • 2
  • 3
  • 7
  • 1
    You cannot use Office Interop from ASP.NET or another server technology. See [Considerations for server-side Automation of Office](http://support.microsoft.com/kb/257757) – John Saunders Mar 06 '13 at 18:24
  • possible dupe of http://stackoverflow.com/q/158706/16391 – StingyJack Mar 06 '13 at 18:24
  • 2
    @JohnSaunders: You *can*, it is just a very, very bad idea ;-) – Eric J. Mar 06 '13 at 18:26
  • @StingyJack: no, that one is not due to asp.net. – John Saunders Mar 06 '13 at 18:26
  • 1
    @roma8716: As John points out, it is not a good idea (at all) to use interop from ASP.Net. I would suggest that you either use a component that is suited to this task (e.g. http://epplus.codeplex.com/), or write a windows service that accepts queued requests to process the Excel files. – Eric J. Mar 06 '13 at 18:27
  • @JohnSaunders. From a .NET perspective, you cannot tell that one or the other is ASP.NET. – StingyJack Mar 06 '13 at 18:28
  • I would use a open source lib like the one here http://stackoverflow.com/questions/151005/create-excel-xls-and-xlsx-file-from-c-sharp – Micah Armantrout Mar 06 '13 at 18:28
  • There is also https://code.google.com/p/excellibrary/ – StingyJack Mar 06 '13 at 18:31
  • @StingyJack: Not true. A desktop application will be running a message pump, and will be in use by a single user at a time, and likely all on the UI thread (which will not be present in ASP.NET). You can try this, but suicide is easier and safer. – John Saunders Mar 06 '13 at 18:31
  • There are no clues in the item I mentioned that indicate the host type. – StingyJack Mar 06 '13 at 18:33
  • @StingyJack: I see your point now. However, from experience, I'm pretty sure that the problem he's seeing is related to using Office Interop in ASP.NET. See also "[Office Interop with 64bit Windows in ASP.NET](http://stackoverflow.com/questions/1031513/office-interop-with-64bit-windows-in-asp-net)" where, big hint, the problem wasn't 64 bits. – John Saunders Mar 06 '13 at 18:36
  • Oh yeah, I avoid Office Interop if I can. Its historically been a painful experience regardless of host. – StingyJack Mar 06 '13 at 18:38
  • @Eric J.:I tried out epplus an everithing was working ok until I needed to insert a new row. The InsertRow method is inserting the row but it is also adding a column to all the following rows of the inserted row. So I'm testing Spreadsheetgera right now and it is working ok but I'd prefer a free option. – roma8716 Mar 08 '13 at 18:20
  • @StingyJack I checked the excellibrary but it only works with .xsl and I need support for .xslx files. – roma8716 Mar 08 '13 at 18:21
  • Check the result from System.Runtime.InteropServices.Marshal.ReleaseComObject, if it is not 0, something else is holding the COM reference. Try FinalReleaseComObject. – Zverev Evgeniy May 12 '16 at 17:08
  • Try using the GC.WaitForPendingFinalizers(); after GC.Collect() – Zverev Evgeniy May 12 '16 at 17:14

8 Answers8

6

Try

Process excelProcess = Process.GetProcessesByName("EXCEL")[0];
if (!excelProcess.CloseMainWindow())
{
 excelProcess.Kill();
}
John Saunders
  • 160,644
  • 26
  • 247
  • 397
Uday
  • 69
  • 1
  • 3
  • Translated into VB.Net Dim excelProcess(0) As Process excelProcess = Process.GetProcessesByName("excel") – Danimal111 Jan 30 '14 at 15:39
  • This is not good for ASP.NET (server) scenario. You are going to kill the instances which appeared after you began handling your request and which are currently handling other requests. – Zverev Evgeniy May 12 '16 at 17:10
  • this should work but if you/(your customer) have another excel worksheet opened that can close it instead of this app – mr R Mar 01 '19 at 07:47
3

This is how I got around this problem:

// Store the Excel processes before opening.
Process[] processesBefore = Process.GetProcessesByName("excel");

// Open the file in Excel.
Application excelApplication = new Application();
Workbook excelWorkbook = excelApplication.Workbooks.Open(Filename);

// Get Excel processes after opening the file.
Process[] processesAfter = Process.GetProcessesByName("excel");

// Now find the process id that was created, and store it.
int processID = 0;
foreach (Process process in processesAfter)
{
    if (!processesBefore.Select(p => p.Id).Contains(process.Id))
    {
        processID = process.Id;
    }
}

// Do the Excel stuff

// Now close the file with the COM object.
excelWorkbook.Close();
excelApplication.Workbooks.Close();
excelApplication.Quit();

// And now kill the process.
if (processID != 0)
{
    Process process = Process.GetProcessById(processID);
    process.Kill();
}
Phil Prett
  • 303
  • 3
  • 8
  • This is nicest as it checks for existing instances first. – Beakie Aug 12 '14 at 09:31
  • 1
    This is not good for ASP.NET (server) scenario. You are going to kill the instances which appeared after you began handling your request and which are currently handling other requests. – Zverev Evgeniy May 12 '16 at 17:09
3

Have a look here: How can I get the ProcessID (PID) for a hidden Excel Application instance

You can track down your ProcessID via GetWindowThreadProcessId API and than kill the process that particularly matches your instance of Excel Application object.

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

Process GetExcelProcess(Microsoft.Office.Interop.Excel.Application excelApp)
{
     int id;
     GetWindowThreadProcessId(excelApp.Hwnd, out id);
     return Process.GetProcessById(id);
}

void TerminateExcelProcess(Microsoft.Office.Interop.Excel.Application excelApp)
{
     var process = GetExcelProcess(excelApp);
     if (process != null)
     {
          process.Kill();
     }
}
Community
  • 1
  • 1
Zverev Evgeniy
  • 3,643
  • 25
  • 42
  • I don't want to import dll. Is there any other way to do the trick. – Umer Farooq Jan 10 '17 at 11:27
  • @UmerFarooq You can iterate through all the `Process` instances and check the `Process.MainWindowHandle` against the `excelApp.Hwnd`. All `Process` instances are available through the `Process.GetProcesses` – Zverev Evgeniy Jan 11 '17 at 12:40
1

I created this method for it, in my tests it works.

private void ClearMemory(Application excelApp) {
    excelApp.DisplayAlerts = false;
    excelApp.ActiveWorkbook.Close(0);
    excelApp.Quit();
    Marshal.ReleaseComObject(excelApp);
}
Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
Guilherme Golfetto
  • 510
  • 2
  • 5
  • 25
0

Try using Open XML SDK 2.0 for Microsoft Office library to create excel document instead of using interop assemblies. It runs a lot faster in my experience and it's easier to use.

JdMR
  • 1,268
  • 14
  • 9
0

Here is the VB version -- I have a large project that uses this and not the time to convert to a better system, so here is the same answer... in vb.NET

Use this to get the process ID (Prior to opening the excel sheet)

Dim excelProcess(0) As Process
excelProcess = Process.GetProcessesByName("excel")

After you're done with your sheet:

xlWorkBook.Close(SaveChanges:=False)
xlApp.Workbooks.Close()
xlApp.Quit()
'Kill the process
If Not excelProcess(0).CloseMainWindow() Then
    excelProcess(0).Kill()
End If
Danimal111
  • 1,976
  • 25
  • 31
  • This is not good for ASP.NET (server) scenario. You are going to kill the instances which appeared after you began handling your request and which are currently handling other requests. – Zverev Evgeniy May 12 '16 at 17:17
  • Thanks @ZverevEugene - Do you point me in the direction of a better way to code this? – Danimal111 Jun 01 '16 at 16:08
  • Actually, yes. Have a look: http://stackoverflow.com/questions/37636319/how-can-i-get-the-processid-pid-for-a-hidden-excel-application-instance – Zverev Evgeniy Jun 06 '16 at 16:44
0

Simple rule: avoid using double-dot-calling expressions, such as this:

var workbook = excel.Workbooks.Open(/*params*/)

(Reference)

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
CHRISQQ
  • 125
  • 1
  • 9
-2
oWB.Close(false, strCurrentDir + strFile, Type.Missing);
oWB.Dispose();
wbks.Close();
wbks.Dispose();
oXL.Quit();
oXL.Dispose();


System.Runtime.InteropServices.Marshal.ReleaseComObject(oXL);
System.Runtime.InteropServices.Marshal.ReleaseComObject(wbks);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oWB);
user2140261
  • 7,855
  • 7
  • 32
  • 45