I'm having an issue with Excel Interop.
The Excel.exe doesn't close even if when I realease instances.
Here is my code :
using xl = Microsoft.Office.Interop.Excel;
xl.Application excel = new xl.Application();
excel.Visible = true;
excel.ScreenUpdating = false;
if (wordFile.Contains(".csv") || wordFile.Contains(".xls"))
{
//typeExcel become a string of the document name
string typeExcel = wordFile.ToString();
xl.Workbook workbook = excel.Workbooks.Open(typeExcel,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing, oMissing, oMissing,
oMissing, oMissing);
object outputFileName = null;
if (wordFile.Contains(".xls"))
{
outputFileName = wordFile.Replace(".xls", ".pdf");
}
else if (wordFile.Contains(".csv"))
{
outputFileName = wordFile.Replace(".csv", ".pdf");
}
workbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outputFileName,
XlFixedFormatQuality.xlQualityStandard, oMissing,
oMissing, oMissing, oMissing, oMissing, oMissing);
object saveChanges = xl.XlSaveAction.xlDoNotSaveChanges;
((xl._Workbook)workbook).Close(saveChanges, oMissing, oMissing);
Marshal.ReleaseComObject(workbook);
workbook = null;
}
I saw that, with the Marshal.RealeaseComObject it should be work, but nothing.
How can I fix this?
Thank you.
Simple rule: avoid using double-dot-calling expressions, such as this:
var workbook = excel.Workbooks.Open(/*params*/)
...because in this way you create RCW objects not only for workbook, but for Workbooks, and you should release it too (which is not possible if a reference to the object is not maintained).
So, the right way will be:
var workbooks = excel.Workbooks;
var workbook = workbooks.Open(/*params*/)
//business logic here
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excel);
Here is a snippet of code I wrote, because I had the same problem as you. Basically, you need to close the workbook, quit the application, and then release ALL of your COM objects (not just the Excel Application object). Finally, call the garbage collector for good measure.
/// <summary>
/// Disposes the current <see cref="ExcelGraph" /> object and cleans up any resources.
/// </summary>
public void Dispose()
{
// Cleanup
xWorkbook.Close(false);
xApp.Quit();
// Manual disposal because of COM
while (Marshal.ReleaseComObject(xApp) != 0) { }
while (Marshal.ReleaseComObject(xWorkbook) != 0) { }
while (Marshal.ReleaseComObject(xWorksheets) != 0) { }
while (Marshal.ReleaseComObject(xWorksheet) != 0) { }
while (Marshal.ReleaseComObject(xCharts) != 0) { }
while (Marshal.ReleaseComObject(xMyChart) != 0) { }
while (Marshal.ReleaseComObject(xGraph) != 0) { }
while (Marshal.ReleaseComObject(xSeriesColl) != 0) { }
while (Marshal.ReleaseComObject(xSeries) != 0) { }
xApp = null;
xWorkbook = null;
xWorksheets = null;
xWorksheet = null;
xCharts = null;
xMyChart = null;
xGraph = null;
xSeriesColl = null;
xSeries = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
Rules - never use no more that one dot
-- one dot
var range = ((Range)xlWorksheet.Cells[rowIndex, setColumn]);
var hyperLinks = range.Hyperlinks;
hyperLinks.Add(range, data);
-- Two or more dots
(Range)xlWorksheet.Cells[rowIndex, setColumn]).Hyperlinks.Add(range, data);
-- Example
using Microsoft.Office.Interop.Excel;
Application xls = null;
Workbooks workBooks = null;
Workbook workBook = null;
Sheets sheets = null;
Worksheet workSheet1 = null;
Worksheet workSheet2 = null;
workBooks = xls.Workbooks;
workBook = workBooks.Open(workSpaceFile);
sheets = workBook.Worksheets;
workSheet1 = (Worksheet)sheets[1];
// removing from Memory
if (xls != null)
{
foreach (Microsoft.Office.Interop.Excel.Worksheet sheet in sheets)
{
ReleaseObject(sheet);
}
ReleaseObject(sheets);
workBook.Close();
ReleaseObject(workBook);
ReleaseObject(workBooks);
xls.Application.Quit(); // THIS IS WHAT IS CAUSES EXCEL TO CLOSE
xls.Quit();
ReleaseObject(xls);
sheets = null;
workBook = null;
workBooks = null;
xls = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
It is tricky to get rid of all references since you have to guess if calls like:
var workbook = excel.Workbooks.Open("")
Creates an instance of Workbooks that you do not hold a reference to.
Even references like:
targetRange.Columns.AutoFit()
Will create an instance of .Columns() without you knowing and not released properly.
I ended up writing a class holding a list of object references that could dispose all objects in reverse order.
The class has a list of objects and Add() functions for anything you reference as you use Excel interop that returns the object itself:
public List<Object> _interopObjectList = new List<Object>();
public Excel.Application add(Excel.Application obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Range add(Excel.Range obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbook add(Excel.Workbook obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheet add(Excel.Worksheet obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Worksheets add(Excel.Worksheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Sheets add(Excel.Sheets obj)
{
_interopObjectList.Add(obj);
return obj;
}
public Excel.Workbooks add(Excel.Workbooks obj)
{
_interopObjectList.Add(obj);
return obj;
}
Then to unregister objects I used the following code:
//Release all registered interop objects in reverse order
public void unregister()
{
//Loop object list in reverse order and release Office object
for (int i=_interopObjectList.Count-1; i>=0 ; i -= 1)
{ ReleaseComObject(_interopObjectList[i]); }
//Clear object list
_interopObjectList.Clear();
}
/// <summary>
/// Release a com interop object
/// </summary>
/// <param name="obj"></param>
public static void ReleaseComObject(object obj)
{
if (obj != null && InteropServices.Marshal.IsComObject(obj))
try
{
InteropServices.Marshal.FinalReleaseComObject(obj);
}
catch { }
finally
{
obj = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
Then principle is to create the class and capture references like this:
//Create helper class
xlsHandler xlObj = new xlsHandler();
..
//Sample - Capture reference to excel application
Excel.Application _excelApp = xlObj.add(new Excel.Application());
..
//Sample - Call .Autofit() on a cell range and capture reference to .Columns()
xlObj.add(_targetCell.Columns).AutoFit();
..
//Release all objects collected by helper class
xlObj.unregister();
Not perhaps code of great beauty but may inspire to something useful.
In your code you have:
excel.Workbooks.Open(...)
excel.Workbooks is creating a COM object. You are then calling the Open function from that COM object. You are not, however, releasing the COM object when you have finished.
This is a common issue when dealing with COM objects. Basically, you should never have more than one dot in your expression because you will need to clean up the COM objects when you've finished.
The topic is simply too big to explore completely in an answer, but I think you'll find Jake Ginnivan's article on the subject extremely helpful: VSTO and COM Interop
If you get tired of all those ReleaseComObject calls, you may find this question helpful:
How to properly clean up Excel interop object in C#, 2012 edition
As stated in other answers, using two dots will create hidden references that cannot be closed by Marshal.FinalReleaseComObject. I just wanted to share my solution, which eliminates the need to remember Marshal.FinalReleaseComObject - it's really easy to miss, and a pain to locate the culprit.
I use a generic IDisposable wrapper class which can be used on any COM object. It works like a charm, and it keeps everything nice and clean. I can even reuse private fields (e.g. this.worksheet). It also auto-releases the object when something throws an error, due to the nature of IDisposable (the Dispose method runs as a finally).
using Microsoft.Office.Interop.Excel;
public class ExcelService
{
private _Worksheet worksheet;
private class ComObject<TType> : IDisposable
{
public TType Instance { get; set; }
public ComObject(TType instance)
{
this.Instance = instance;
}
public void Dispose()
{
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(this.Instance);
}
}
public void CreateExcelFile(string fullFilePath)
{
using (var comApplication = new ComObject<Application>(new Application()))
{
var excelInstance = comApplication.Instance;
excelInstance.Visible = false;
excelInstance.DisplayAlerts = false;
try
{
using (var workbooks = new ComObject<Workbooks>(excelInstance.Workbooks))
using (var workbook = new ComObject<_Workbook>(workbooks.Instance.Add()))
using (var comSheets = new ComObject<Sheets>(workbook.Instance.Sheets))
{
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet1"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Action";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet2"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "Status";
this.worksheet.Visible = XlSheetVisibility.xlSheetHidden;
}
using (var comSheet = new ComObject<_Worksheet>(comSheets.Instance["Sheet3"]))
{
this.worksheet = comSheet.Instance;
this.worksheet.Name = "ItemPrices";
this.worksheet.Activate();
using (var comRange = new ComObject<Range>(this.worksheet.Range["A4"]))
using (var comWindow = new ComObject<Window>(excelInstance.ActiveWindow))
{
comRange.Instance.Select();
comWindow.Instance.FreezePanes = true;
}
}
if (this.fullFilePath != null)
{
var currentWorkbook = (workbook.Instance as _Workbook);
currentWorkbook.SaveAs(this.fullFilePath, XlFileFormat.xlWorkbookNormal);
currentWorkbook.Close(false);
}
}
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
throw;
}
finally
{
// Close Excel instance
excelInstance.Quit();
}
}
}
}
Alternatively, you can kill the Excel process as explained here.
First, import SendMessage function:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
Then, send the WM_CLOSE message to the main window:
SendMessage((IntPtr)excel.Hwnd, 0x10, IntPtr.Zero, IntPtr.Zero);
Cannot close Excel.exe after Interop process
Don't make this too complicated!!
Just create a simple method and call that method as
follows :
// to kill the EXCELsheet file process from process Bar
private void KillSpecificExcelFileProcess() {
foreach (Process clsProcess in Process.GetProcesses())
if (clsProcess.ProcessName.Equals("EXCEL")) //Process Excel?
clsProcess.Kill();
}
#Denis Molodtsov in an attempt to be helpful suggested killing all processes named 'EXCEL'. That seems to be asking for trouble. There are already many answers that describe ways to get the process to stop after the call to excel.quit() by playing nice with COM interop. This is best if you can make it work.
#Kevin Vuilleumier had a great suggestion to send WM_CLOSE to the Excel window. I plan to test this.
If for some reason you need to kill an Excel App Object's Excel process, you can target it specifically using something like this:
using System.Diagnostics;
using System.Runtime.InteropServices;
// . . .
[DllImport("user32.dll", SetLastError=true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
// . . .
uint excelAppPid;
uint tid = GetWindowThreadProcessId(excel.Hwnd, out excelAppPid);
if (tid)
{
Process excelAppProc = Process.GetProcessById($excelPid);
if (excelAppProc)
{
excelAppProc.Kill();
}
}
I don't have time to fully test in C#, but I ran a quick test in Powershell where I'm having a problem with Excel not terminating and this approach works.
It's pretty straightforward. Excel App object's Hwnd property is the Excel process's hidden window handle. Pass excel.Hwnd to GetWindowThreadProcessId to get the process ID. Use that to open the process, finally invoke Kill().
At least we're sure we're killing the right process. Well, pretty sure. If the Excel process already terminated normally, it's process ID could be reused by a new process. To limit this possibility, it's important not to wait between calling excel.quit() and attempting to kill.
In case you are desperate. Do not use this approach unless you understand what it does:
foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
{
proc.Kill();
}
Note: This kill every process named "EXCEL".
I had to do it becase even though I've closed every single COM object in my code I still had stubborn Excel.exe process just hanging there. This is by no means the best solution, of course.
I had same issue , we can solve the issue without any killing, we always forget to close interfaces which we have used form Microsoft.Office.Interop.Excel class so here is the code snippet and follow the structure and way have cleared objects , also keep an eye on Sheets interface in your code this is the main culprit we often close the application,Workbook,workbooks,range,sheet but we forget or unknowingly dont release the Sheets object or used interface so here is the code :
Microsoft.Office.Interop.Excel.Application app = null;
Microsoft.Office.Interop.Excel.Workbooks books = null;
Workbook book = null;
Sheets sheets = null;
Worksheet sheet = null;
Range range = null;
try
{
app = new Microsoft.Office.Interop.Excel.Application();
books = app.Workbooks;
book = books.Add();
sheets = book.Sheets;
sheet = sheets.Add();
range = sheet.Range["A1"];
range.Value = "Lorem Ipsum";
book.SaveAs(#"C:\Temp\ExcelBook" + DateTime.Now.Millisecond + ".xlsx");
book.Close();
app.Quit();
}
finally
{
if (range != null) Marshal.ReleaseComObject(range);
if (sheet != null) Marshal.ReleaseComObject(sheet);
if (sheets != null) Marshal.ReleaseComObject(sheets);
if (book != null) Marshal.ReleaseComObject(book);
if (books != null) Marshal.ReleaseComObject(books);
if (app != null) Marshal.ReleaseComObject(app);
}
After doing several tests on my own, checking different answers, this is the shortest code that makes the process go away just a few seconds later:
var excelApp = new Microsoft.Office.Interop.Excel.Application();
var workbooks = excelApp.Workbooks;
try
{
var wb = workbooks.Open(filePath);
// Use worksheet, etc.
Worksheet sheet = wb.Worksheets.get_Item(1);
}
finally
{
excelApp.Quit();
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excelApp);
}
Despite the messages about the double-dot myth, in my own tests, if I don't have a variable for Workbooks, the process would stay forever. It seems that indeed calling excelApp.Workbooks creates some objects in memory which prevent the Garbage Collector from disposing excel.exe. This means that this leaves the process in memory:
try
{
// Do not
var wb = excelApp.Workbooks.Open("");
}
finally
{
excelApp.Quit();
// Do not
Marshal.ReleaseComObject(excelApp.Workbooks);
Marshal.ReleaseComObject(excelApp);
}
This code worked on me.
Excel.Application excelApp = null;
Excel.Workbooks excelWorkbooks = null;
Excel.Workbook excelWorkbook = null;
Excel._Worksheet xlWorkSheet = null;
Excel.Range range = null;
excelApp = new Excel.Application();
excelWorkbooks = excelApp.Workbooks;
excelWorkbook = excelWorkbooks.Open(excelName);
xlWorkSheet = (Excel.Worksheet)excelWorkbook.ActiveSheet;
range = xlWorkSheet.Range["C3"] ;
range.Value = "Update Data";
Marshal.ReleaseComObject(range);
xlWorkSheet.SaveAs(path);
Marshal.ReleaseComObject(xlWorkSheet);
excelWorkbook.Close();
Marshal.ReleaseComObject(excelWorkbook);
excelWorkbooks.Close();
Marshal.ReleaseComObject(excelWorkbooks);
excelApp.Quit();
Marshal.ReleaseComObject(excelApp);
Inspired by #jimhark solution: In my program, I have to open simultaneously multiple Excel files. Therefore I had to tidy up some codes.
public void DoSomeExcelWork()
{
OpenExcelFile(filePath, out Application excelApp, out Workbook workbook, out Process process);
// do some work on your excel.
DisposeExcelFile(excelApp, workbook, process, false);
}
This is where the excel file gets opened.
private static void OpenExcelFile(string excelFilePath, out Application excelApp, out Workbook workbook, out Process process)
{
excelApp = new Application();
workbook = excelApp.Workbooks.Open(excelFilePath);
process = ProcessUtility.GetExcelProcess(excelApp);
}
You don't need to call Marshal stuff, since the process gets killed directly.
private static void DisposeExcelFile(Application excelApp, Workbook workbook, Process process, bool shouldSave = true)
{
if (shouldSave)
workbook.Save();
excelApp.Application.Quit();
process.Kill();
}
This is where #jimhark 's solution comes in.
public static class ProcessUtility
{
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
public static Process GetExcelProcess(Microsoft.Office.Interop.Excel.Application excelApp)
{
int id;
GetWindowThreadProcessId(excelApp.Hwnd, out id);
return Process.GetProcessById(id);
}
}
I have been plagued with this issue for years and finally came up with a good solution that should work for all use cases that I can think of. Whether you want your application to close the process after generating and saving or waiting until the user closes the window, along with ability to have multiple excel instances and never having the process linger.
Create this class:
using System;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
namespace YourNameSpace
{
public class MicrosoftApplications
{
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
public class Excel
{
public Excel()
{
Application = new Microsoft.Office.Interop.Excel.Application();
RegisterExitEvent();
}
public Microsoft.Office.Interop.Excel.Application Application;
private void RegisterExitEvent()
{
Application.WindowDeactivate -= XlApp_WindowDeactivate;
Application.WindowDeactivate += XlApp_WindowDeactivate;
}
private void XlApp_WindowDeactivate(Workbook Wb, Window Wn)
{
Kill();
}
public void Kill()
{
int pid = 0;
GetWindowThreadProcessId(Application.Hwnd, out pid);
if (pid > 0)
{
System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(pid);
p.Kill();
}
Application = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
}
And you can call it by:
YourNameSpace.MicrosoftApplications.Excel xlApp = new YourNameSpace.MicrosoftApplications.Excel();
Do whatever you need to do by calling xlApp.Application.whatever instead of xlApp.whatever and if the user exits the excel window(s) it will kill the process(es) that were used in the code. If you want to just generate a report behind the scenes but not display the form, then simply call xlApp.Kill(); to end that specific process.
Hope this helps someone, wish I knew this about 10 years ago.
İf you are handling it in one button u guys can get lastest process which you created and you can kill it.I used it.Have a good days.
//Exporting excel codes are here
System.Diagnostics.Process [] proc = System.Diagnostics.Process.GetProcessesByName("excel");
proc[proc.Length-1].Kill();
Need to work with Excel Interop. I can successfully open and read from an excel file but while closing it, the background process for that excel does not get killed. Tried using several solutions from previous SO links, but no luck! So my ask is, how to kill the background process???
Below is the UPDATED CODE that I am currently using:
Excel.Application application = new Excel.Application();
var workbooks = application.Workbooks;
Excel.Workbook workbook = workbooks.Open(path);
Excel.Worksheet worksheet = workbook.ActiveSheet;
Excel.Range range = worksheet.UsedRange;
var rows = range.Rows;
// Some business logic
for (int row = 2; row <= rows.Count; row++)
{
//Read the data from the excel
}
// Some business logic
//close the excel
rows.Clear();
cell.Clear();
range.Clear();
workbook.Close(false);
application.Quit();
while (Marshal.FinalReleaseComObject(rows) != 0) { }
while (Marshal.FinalReleaseComObject(cell) != 0) { }
while (Marshal.FinalReleaseComObject(range) != 0) { }
while (Marshal.FinalReleaseComObject(worksheet) != 0) { }
while (Marshal.FinalReleaseComObject(workbook) != 0) { }
while (Marshal.FinalReleaseComObject(workbooks) != 0) { }
while (Marshal.FinalReleaseComObject(application) != 0) { }
rows = null;
cell = null;
range = null;
worksheet = null;
workbook = null;
workbooks = null;
application = null;
GC.Collect();
GC.WaitForPendingFinalizers();
By following the above code, I get the below exception in my debugger:
Any help on this will be appreciated.
You are using range.Rows.Count, this might violate the "Never use 2 dots with com objects." rule. See here
You could try including this ;
var rows = range.Rows
for (int row = 2; row <= rows.Count; row++)
{
//Read the data from the excel
}
rows.Clear(); //rows is itself a range object
The few times I've had to use Excel interop, I haven't had any issues when following these simple rules:
Always wrap any Excel interop in try-finally
blocks. In the finally block put all releasing logic.
Use Marshal.FinalReleaseComObject to release named COM
references as its essentially doing the ref count loop for you.
Eagerly release COM objects from deepest to
shallowest. In your case I'd start with range then worksheet
then workbook and so on.
Correctly release unreferenced COM objects (two dot rule) with GC.Collect() and GC.WaitForPendingFinalizers(). Do this before manually
releasing pending COM objects you hold a named reference to.
So I am doing a few For loops that iterate through different variable changes imported from a template, a SQL script run, data is dumped, loop to next SQL script, etc, and this is done multiple times using the same template file (which at the end of the query dumps is saved as a new file. With that as background:
I need to be able to open the template the first time through the loop, then keep it open through each query dump until done. I don't want to keep reopening the file as its just too big and cumbersome I have this so far:
public void ExportToExcel(DataSet dataSet, string templatePath)
{
Excel.Application excelApp = new Excel.Application();
excelApp.Visible = true;
FileInfo excelFileInfo = new FileInfo(templatePath);
Boolean fileOpenTest = IsFileLocked(excelFileInfo);
if (!fileOpenTest)
{
Excel.Workbook templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
Excel.Workbook templateBook = excelApp.Workbooks[templatePath];
}
for (int i = 0; i < lstQueryDumpSheet.Items.Count; i++)
{
string tabName = lstQueryDumpSheet.Items[i].ToString();
Excel.Worksheet templateSheet = templateBook.Sheets[tabName];
// Copy DataTable
foreach (System.Data.DataTable dt in dataSet.Tables)
{ ... rest of loops...
My Problem is that the code line "Excel.Worksheet templateSheet = templateBook.Sheets[tabName];" tells me that "templateBook" is not assigned, but I am assigning it outside the IF statement so it should pass....right?
This is a problem of scope. Your code:
if (!fileOpenTest)
{
Excel.Workbook templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
Excel.Workbook templateBook = excelApp.Workbooks[templatePath];
}
declares the templateBook variable inside the blocks and thus its scope is limited to within that block. To have the variable persist outside of those blocks you need to declare it outside like this:
Excel.Workbook templateBook;
if (!fileOpenTest)
{
templateBook = excelApp.Workbooks.Open(templatePath);
}
else
{
templateBook = excelApp.Workbooks[templatePath];
}
I've got a Class report witch includes a method RunExcelReporting. It knows what workbook to open and what routines to run via a control table.
As a test I've just spelled one of the macros incorrectly in the control table.
The console application creates an instance of report and runs the RunExcelReporting method but then stops at a point where Excel is diplaying the message box which asks if the user would like to save the workbook. This is a bad halfway house point for me - I'd rather have some sort of error message OR I'd like the workbook to close without saving changes. How can I amend the following code to help?
private void RunExcelReporting(int x) {
Excel.Application excelApp = null;
Excel.Workbook book = null;
try {
excelApp = new Excel.Application();
excelApp.Visible = true;
book = excelApp.Workbooks.Open((string)MyReportRow["XLfile" + x + "_Path"] + (string)MyReportRow["XLfile" + x + "_Name"]);//,null,true);
//loop through the four possible macros in each book
for (int j = 1; j <= 4; j++) {
if (IsNull(MyReportRow["XLfile" + x + "_Macro" + j]) == false) {
excelApp.Run((string)MyReportRow["XLfile" + x + "_Macro" + j]);
}
}
book.Close(false, Type.Missing, Type.Missing);
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
finally {
if (book != null) {
//book.Close(false, Type.Missing, Type.Missing);
book = null;
}
if (excelApp != null) {
excelApp.Application.Quit();
excelApp.Quit();
excelApp = null;
}
}
}
Try to add (somewhere before book.Close):
excelApp.DisplayAlerts = false
Seems that the problem is that Excel isn't bubbling up the error to the console; the error is happening within Excel i.e a separate process to the console.
I'll need to extent the application quite a bit to account for this.
I am not able to get my EXCEL (32) process closed once I am done using it.
As you can see in the code below, once ProcessRFAFData function finishes its execution, the EXCEL process doesn't get closed (I can still see EXCEL.EXE*32 in the task manager).
For this reason, when SaveErrors starts its execution, I get the following exception:
System.Runtime.InteropServices.COMException (0x800A03EC):
Microsoft Office Excel cannot open or save any more documents because there is not enough available memory or disk space.
• To make more memory available, close workbooks or programs you no longer need.
• To free disk space, delete files you no longer need from the disk you are saving to.
at Microsoft.Office.Interop.Excel.Workbooks.Add(Object Template)
at NextG.RFAFImport.Layouts.NextG.RFAFImport.RFAFDataImporter.<>c__DisplayClass9.b__6()
at Microsoft.SharePoint.SPSecurity.<>c__DisplayClass4.b__2()
at Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode)
at Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(WaitCallback secureCode, Object param)
at Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(CodeToRunElevated secureCode)
at NextG.RFAFImport.Layouts.NextG.RFAFImport.RFAFDataImporter.SaveErrors()
Here is the code that executes the Excel processes:
try {
ProcessRFAFData(FileName);
} catch (Exception ex) {
Status = "ERROR: " + ex.ToString();
}
if (Errors.Count() > 0) {
SaveErrors();
}
Here are all the functions interacting with Excel:
private void ReleaseObject(object obj) {
try {
System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
obj = null;
} catch (Exception) { } finally {
GC.Collect();
}
}
private void ProcessRFAFData(string FileName) {
Microsoft.Office.Interop.Excel.Application XLA = null;
Microsoft.Office.Interop.Excel.Workbook XLW = null;
Microsoft.Office.Interop.Excel.Worksheet XLS = null;
bool error = false;
try {
SPSecurity.RunWithElevatedPrivileges(delegate() {
XLA = new Microsoft.Office.Interop.Excel.Application();
XLW = XLA.Workbooks.Open(FileName, 0, true,
Type.Missing, null, null, true, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows,
Type.Missing, false, false, Type.Missing, false, Type.Missing, Type.Missing);
int index = RFAFTabExists(ref XLW);
if (index == 0) return;
XLS = (Microsoft.Office.Interop.Excel.Worksheet)XLW.Worksheets.get_Item(index);
if (!ValidProjectID(ref XLS)) return;
ParseData(ref XLS);
XLW.Close(true, Type.Missing, Type.Missing);
XLA.Quit();
ReleaseObject(XLS);
ReleaseObject(XLW);
ReleaseObject(XLA);
});
} catch (SP.ServerException ex) {
// output error
} catch (Exception ex) {
// output error
}
}
private int RFAFTabExists(ref Microsoft.Office.Interop.Excel.Workbook XLW) {
int index = 0;
foreach (Microsoft.Office.Interop.Excel.Worksheet w in XLW.Worksheets) {
if (w.Name.Equals(settings.Collection["RFAFTabName"])) index++;
}
return index;
}
private bool ValidProjectID(ref Microsoft.Office.Interop.Excel.Worksheet XLS) {
using (SP.ClientContext CTX = new SP.ClientContext(SiteURL)) {
var projectId = XLS.Cells.get_Range(settings.Collection["ProjectIDCell"], Type.Missing).Text.ToString();
var project = // getting list of projects from SharePoint
if (project.Count() > 0) {
ProjectID = XLS.Cells.get_Range(settings.Collection["ProjectIDCell"], Type.Missing).Text.ToString();
return true;
}
}
return false;
}
private void ParseData(ref Microsoft.Office.Interop.Excel.Worksheet XLS) {
ListData.Add("HID", GetHID(XLS.Cells.get_Range(settings.Collection["HIDCell"],
Type.Missing).Text.ToString()));
if (ListData["HID"].Equals("0")) Errors.Add(new ImportError {
Reason = "Hub ID does not exist in this project workspace.",
Reference = string.Format("Hub ID: {0}", XLS.Cells.get_Range(settings.Collection["HIDCell"],
Type.Missing).Text.ToString())
});
int row = Int32.Parse(settings.Collection["StartRow"]);
while (!NoMoreData(ref XLS, row)) {
string PRSIN = XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["PRSIN"], row), Type.Missing).Text.ToString();
string NOC = ValidateNumber(XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["NOC"], row), Type.Missing).Text.ToString());
string UEIRP = ValidateNumber(XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["UEIRP"], row), Type.Missing).Text.ToString());
string LAT = ValidateLatLon(XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["LAT"], row), Type.Missing).Text.ToString());
string LON = ValidateLatLon(XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["LON"], row), Type.Missing).Text.ToString());
string PJ = GetPJ(XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["JurisdictionCol"], row), Type.Missing).Text.ToString(),
XLS.Cells.get_Range(string.Format("{0}{1}", settings.Collection["StateCol"], row),
Type.Missing).Text.ToString());
string ST = GetState(XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["JurisdictionCol"], row), Type.Missing).Text.ToString(),
XLS.Cells.get_Range(string.Format("{0}{1}", settings.Collection["StateCol"], row),
Type.Missing).Text.ToString());
ListItemData.Add(new ListItem {
ProposedRemoteSiteItemNumber = PRSIN,
NumberOfCarriers = NOC,
UsableEIRP = UEIRP,
Latitude = LAT,
Longitude = LON,
PrimaryJurisdiction = PJ,
State = ST
});
row++;
}
}
private bool NoMoreData(ref Microsoft.Office.Interop.Excel.Worksheet XLS, int row) {
return string.IsNullOrEmpty(XLS.Cells.get_Range(string.Format("{0}{1}",
settings.Collection["ProposedRemoteSiteItemNumberCol"], row), Type.Missing).Text.ToString());
}
private void SaveErrors() {
Microsoft.Office.Interop.Excel.Application XLA = null;
Microsoft.Office.Interop.Excel.Workbook XLW = null;
Microsoft.Office.Interop.Excel.Worksheet XLS = null;
object MissingValue = System.Reflection.Missing.Value;
try {
try {
SPSecurity.RunWithElevatedPrivileges(delegate() {
XLA = new Microsoft.Office.Interop.Excel.Application();
XLW = XLA.Workbooks.Add(MissingValue);
XLS = (Microsoft.Office.Interop.Excel.Worksheet)XLW.Worksheets.get_Item(1);
XLS.Cells[1, 1] = "Reason for error";
XLS.Cells[1, 2] = "Reference";
XLS.get_Range("A1").Font.Bold = true;
XLS.get_Range("B1").Font.Bold = true;
int row = 2;
foreach (ImportError e in Errors) {
XLS.Cells[row, 1] = e.Reason;
XLS.Cells[row, 2] = e.Reference;
row++;
}
XLW.SaveAs(ErrorLogFileName, Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal,
MissingValue, MissingValue, MissingValue, MissingValue,
Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlExclusive, MissingValue,
MissingValue, MissingValue, MissingValue, MissingValue);
XLW.Close(true, MissingValue, MissingValue);
XLA.Quit();
ReleaseObject(XLS);
ReleaseObject(XLW);
ReleaseObject(XLA);
});
} catch (Exception ex) {
Status = "ERROR: " + ex.ToString();
}
// Uploading excel file to SharePoint document library
} catch (Exception) { }
}
You may have to go ridiculously explicit:
excelWorkbook.Close (false, System.Reflection.Missing.Value,System.Reflection.Missing.Value) ;
excelWorkbooks.Close();
excelApp.Quit();
Marshal.ReleaseComObject(excelWorksheet);
Marshal.ReleaseComObject(excelSheets);
Marshal.ReleaseComObject(excelWorkbooks);
Marshal.ReleaseComObject(excelWorkbook);
Marshal.ReleaseComObject(excelApp);
excelWorksheet = null;
excelSheets = null;
excelWorkbooks = null;
excelWorkbook = null;
excelApp = null;
GC.GetTotalMemory(false);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.GetTotalMemory(true);
I've encountered situations where even that did not do it. I resorted to hunting down the Excel process and called Kill() on it.
Release your Excel objects in a finally block, in case of exceptions.
try
{
...
}
catch
{
...
}
finally
{
ReleaseObject(XLS);
ReleaseObject(XLW);
ReleaseObject(XLA);
}
First suggestion: http://code.google.com/p/excellibrary This is a great library that I have used with a lot of success.
Second suggestion: If you absolutely MUST close Excel
/// <summary>
/// Gets all currently running instances of Excel, so we don't kill active windows.
/// </summary>
private void GetInstancesToSave()
{
if (_instancesToSaveList != null)
{
_instancesToSaveList.Clear();
}
_instancesToSaveList = Process.GetProcesses().ToList<Process>();
_instancesToSaveList = _instancesToSaveList.FindAll(proc => proc.ProcessName == "EXCEL");
}
/// <summary>
/// Kills any instances of Excel that were created by the program.
/// </summary>
/// <param name="zInstancesToSave">Instances that were not </param>
private static void KillExcel(List<Process> zInstancesToSave)
{
List<Process> xProcesslist = Process.GetProcesses().ToList<Process>();
xProcesslist = xProcesslist.FindAll(proc => proc.ProcessName == "EXCEL");
foreach (Process xTheprocess in xProcesslist)//read through all running programs
{
bool killit = true;
foreach (Process proc in zInstancesToSave)//read through all running programs
{
if (xTheprocess.Id == proc.Id)
{
killit = false;
}
}
if (killit)
{
xTheprocess.Kill();
}
}
}
You can use these 2 methods to keep track of which instances of excel are running when you start and then find any instances of Excel that your app opened and then kill them. It's certainly not a great solution but sometimes you just have to bite the bullet.
If you don't care about prior instances you can also just do:
/// <summary>
/// Kills any instances of Excel that were created by the program.
/// </summary>
/// <param name="zInstancesToSave">Instances that were not </param>
private static void KillExcel(List<Process> zInstancesToSave)
{
List<Process> xProcesslist = Process.GetProcesses().ToList<Process>();
xProcesslist = xProcesslist.FindAll(proc => proc.ProcessName == "EXCEL");
foreach(Proc process in xProcesslist)
{
process.Kill();
}
}
I would look into using EPPlus from Code Plex.
This example shows how to read data. You are always better on a server going this route - only issue is if you need call formulas - then this approach will not work.
Most of your code will be very similar to your current code, so I would estimate a few hours for moving to this library.
In my case, the Excel add-in lived in a separate AppDomain that I had created earlier on but when the Add-in was unloaded, I never called:
childDomain.Unload();
Once called, the Excel Zombie process was no longer there...
In addition to the above solutions and here is my LOGICALsolution.
I was thinking why do I have so many EXCEL.EXE applications and handled this way.
Here i am checking for conditions while creating New Excel application.
if (_masterApp == null)
_masterApp = new EXCEL.Application();
Only create New Instances of Excel application if the variable you want to create is Null, else reassign the existing variable.
and Use the Solutions mentioned in this blog to CLOSE the _masterApp.
Benefits: This way you will only close the EXCEL application you opened through Solution and not close Manually opened Excel Application.
Look at this link
Kill Process Excel C#
Thanks to KD7 for the solution.
One minor modification is that if your excelapp is not visible your mainwindowtitle will be blank. eg. ""
private void KillSpecificExcelFileProcess(string excelFileName)
{
var processes = from p in Process.GetProcessesByName("EXCEL")
select p;
foreach (var process in processes)
{
MessageBox.Show(process.MainWindowTitle);
if (process.MainWindowTitle == excelFileName)
{
process.Kill();
}
}
}
and to kill all zombie excel background process'
KillSpecificExcelFileProcess("");
This gave me no end of trouble in solving so I will be posting it on each zombie excel process thread.