How to fully terminate EXCEL.exe from running tasks in office.interop? - c#

I have to open an excel file and want to get the data to word a document. I use to this code:
private Excel.Application excelapp;
Type ExcelType = Type.GetTypeFromProgID("Excel.Application");
dynamic ExcelInst = Activator.CreateInstance(ExcelType);
this.excelapp = ExcelInst;
this.workbook = this.excelapp.Workbooks.Open(Filename : this.filePath, ReadOnly: true);
I used all the techniques to close/quit/dispose the opened process (except for Process.Kill), but those won't work. How do I fully terminate any running background tasks?

You just need to call the Quit method of the Excel Application class when you are done:
private Excel.Application excelapp;
Type ExcelType = Type.GetTypeFromProgID("Excel.Application");
dynamic ExcelInst = Activator.CreateInstance(ExcelType);
this.excelapp = ExcelInst;
this.workbook = this.excelapp.Workbooks.Open(Filename : this.filePath, ReadOnly: true);
' do whatever you need there
this.workbook.Save();
this.excelapp.Quit();
If unsaved workbooks are open when you use this method, Excel displays a dialog box asking whether you want to save the changes. You can prevent this by saving all workbooks before using the Quit method or by setting the DisplayAlerts property to False. When this property is False, Excel doesn't display the dialog box when you quit with unsaved workbooks; it quits without saving them.
If you set the Saved property for a workbook to True without saving the workbook to the disk, Excel will quit without asking you to save the workbook. Note: this does not save the workbook; it just makes it look like it's saved.

My understanding is that since interop uses “COM” objects, you need to “release" those objects. Something like…
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(xlApp);
in that order. This should release those objects from the background tasks
I have had success with the following approach when using office-interop…
Set up all the Excel/Word/Outlook. Etc.… interop stuff in a try/catch/finally statement. This should almost guarantee that your code will properly close and “release” the resources it uses even if there is an exception. Otherwise, the code will always be susceptible to leaking a resource.
Word.Application app = null;
Word.Document doc = null;
try {
app = new Word.Application();
//app.Visible = true;
doc = app.Documents.Add();
// do some stuff with the document…
doc.SaveAs2(filename);
MessageBox.Show("Document created successfully !");
}
catch (Exception e) {
MessageBox.Show("ERROR: " + e.Message);
}
finally {
if (doc != null) {
doc.Close();
Marshal.ReleaseComObject(doc);
}
if (app != null) {
app.Quit();
Marshal.ReleaseComObject(app);
}
}

I use the following to open excel and read / update it.
Excel.Application app;
Excel.Workbook wb;
private void openAndRead()
{
app = new Excel.Application();
wb = app.Workbooks.Open(YourFileHere);
//do stuff here read or write
//app.ActiveSheet.Cells[1, "A"] = "write this in cell A1";
//string read= app.ActiveSheet.Cells[1, "A"];
}
private void closeExcel ()
{
app.DisplayAlerts = false; //this will stop popup questions from excel
wb.Close();
app.Quit();
}

If you want to be confident that EXCEL.EXE process will go away, there's one way to realize it. May be this is not the elegant solution, but at least it does work. The main idea is to catch the Excel window's handle. Having this handle, you can get its process and shut it down.
WARNING: If you don't make Excel window visible, this method WILL NOT work!
using System.Diagnostics;
using Excel = Microsoft.Office.Interop.Excel;
void Kill_Excel()
{
// Create instance of Excel application.
// If you don't make Excel window visible, this method WILL NOT work!
Excel.Application excel = new Excel.Application { Visible = true };
// Catch Excel window handle
IntPtr hwnd = new IntPtr(excel.Hwnd);
// Get EXCEL.EXE process
Process xlproc = Process.GetProcesses().Where(p => p.MainWindowHandle == hwnd).First();
// Do some operations
Excel.Workbook book = excel.Workbooks.Add();
Excel.Worksheet sheet = book.Sheets[1] as Excel.Worksheet;
sheet.Cells[1, 1] = "Hello!";
// Close Excel
book.Close(SaveChanges: false);
excel.Quit();
// Garbage collection
GC.Collect();
GC.WaitForFullGCComplete();
// Kill process - FOR SURE! :)
xlproc.Kill();
}

Related

Excel Interop, lingering instances of Excel

Simple code, and I thought it should kill Excel. But the task manager says this leaves an instance of Excel running. What am I missing? Thank you.
using Microsoft.Office.Interop.Excel;
//Stuff...
// Launch dialog picker that return path to Excel file, in string
// called excelTemplate
Microsoft.Office.Interop.Excel.Application xlTemp = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = xlTemp.Workbooks.Open(excelTemplate);
xlTemp.DisplayAlerts = false;
// Poke through individual sheets, get some info from them
workbook.Close();
xlTemp.Quit();
Application xlTemp = new Application();
Workbooks workbooks = xlTemp.Workbooks;
Workbook workbook = workbooks.Open(excelTemplate);
// Do stuff.
workbook.Close();
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(workbooks);
xlTemp.Quit();
Marshal.ReleaseComObject(xlTemp);
Aside from needing to release each COM object, note also that by doing xlTemp.Workbooks.Open, you're leaking a COM reference by not assigning xlTemp.Workbooks to a variable that can be released later.
You can used #using to automatically dispose the application.
using(Microsoft.Office.Interop.Excel.Application xlTemp = new
Microsoft.Office.Interop.Excel.Application())
{
Workbook workbook = xlTemp.Workbooks.Open(excelTemplate);
xlTemp.DisplayAlerts = false;
// Poke through individual sheets, get some info from them
workbook.Close();
}

Find and edit open Excel sheet using interop

I have linked an application that gets data from an API, I open the sheet when a new contract is loaded to the program. Now I am trying to write new data to the excel sheet later in the program when i collect new data.
I know how to write data to the excel sheet and how to open the sheet I want to write on. The problem is I don't know how to write to the sheet once its already open, all I can get it to do is open another instance of the sheet.
I need to be able to open the sheet in one void and then update the now open sheet in a later void. How do I check to see if the sheet is open and if it is then access it again to write more data to it?
Here is how I have opened Excel,
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
Console.WriteLine("Opening Excel...");
if (xlApp == null)
{
Console.WriteLine("EXCEL could not be started. Check that your office installation and project references are correct.");
return;
}
xlApp.Visible = true;
Workbook wb = xlApp.Workbooks.Open(#"C:\Users\Craig Key\Desktop\AppExports\TestExport.xlsx");
Console.WriteLine("Opening Currently Linked Workbook...");
Worksheet ws = (Worksheet)wb.ActiveSheet;
Console.WriteLine("Opening Active Worksheet...");
if (ws == null)
{
Console.WriteLine("Worksheet could not be created. Check that your office installation and project references are correct.");
}
Now later I need to find xlApp later in the program and write to it again, without opening another instance of the file.
I figured this out after searching for a while, I needed to use the marsh to try to bind the the open instance and then work with it.
Microsoft.Office.Interop.Excel.Application xlApp = null;
//Microsoft.Office.Interop.Excel.Workbooks wbs = null;
Microsoft.Office.Interop.Excel.Workbook wb = null;
Microsoft.Office.Interop.Excel.Worksheet ws = null;
bool wasFoundRunning = false;
Microsoft.Office.Interop.Excel.Application tApp = null;
//Checks to see if excel is opened
Console.WriteLine("CDC is looking for Excel...");
try
{
tApp = (Microsoft.Office.Interop.Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
wasFoundRunning = true;
}
catch (Exception)//Excel not open
{
wasFoundRunning = false;
}
if (true == wasFoundRunning)
{
//Control Excel
}

Updating existing excel file while it is open

I read a lot about how to communicate from C# to Excel and saw some good references.
The thing is I'm looking for an easy way to update existing excel file while it is still open, using
the most advanced way (linq for example) and not OLEDB.
This should be few lines of code describing how can I read current cell, update his value and take into consideration the file might not be exist, but if it does exist and open, it will just update the file without giving the notification the file is already exist. If the file doesn't exist it will create a new one.
SO:
1. connect to an excel file, check if it exist, if not create one
2. read from cell
3. update cell
4. do this while the excel sheet can be still open wild.
I already visited the following places:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/ef11a193-54f3-407b-9374-9f5770fd9fd7/writing-to-excel-using-c
Updating an excel document programmatically
Update specific cell of excel file using oledb
I used the following code:
if (File.Exists(#"C:\\temp\\test.xls"))
{
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbooks workBooks = excelApp.Workbooks;
Microsoft.Office.Interop.Excel.Workbook workBook = workBooks.Open(#"C:\\temp\\test.xls");
Microsoft.Office.Interop.Excel.Worksheet workSheet = workBook.Worksheets.get_Item(1);
int nColumns = workSheet.UsedRange.Columns.Count;
int nRows = workSheet.UsedRange.Rows.Count;
for (int i = 2; i < nRows; i++)
{
workSheet.Columns["1","A"] = "test";
}
workBook.Save();
workBook.Close();
}
So I use VSTO Contrib to help out with COM Interop and memory management and that's why you see .WithComCleanup().
To open up a spreadsheet:
try
{
using (var xlApp = new Microsoft.Office.Interop.Excel.Application().WithComCleanup())
using (var wrkbooks = xlApp.Resource.Workbooks.WithComCleanup())
using (var wrkbook = wrkbooks.Resource.Open(filePath, false, true).WithComCleanup())
{
If the excel file is already open, then to get around the Read-Only follow this tip:
wrkbooks.Resource.Open(filePath, false, FALSE).WithComCleanup())
Here's how I iterate though the sheets (note that some Excel sheets are ChartSheets):
foreach (object possibleSheet in xlApp.Resource.Sheets)
{
Microsoft.Office.Interop.Excel.Worksheet aSheet = possibleSheet as Microsoft.Office.Interop.Excel.Worksheet;
if (aSheet == null)
continue;
Here is a quick way to get a reference to the sheet you're interested in:
activeSheet = wrkbook.Resource.Sheets[sheetToImport];
You read and write to cells just as you've identified:
for (int i = 2; i < nRows; i++)
{
activeSheet.Columns["1","A"] = "test";
}
Here is how I close Excel:
MathematicaAPI.XlHelper.CloseExcel((Worksheet)activeSheet, (Workbook)wrkbook.Resource , (Workbooks)wrkbooks.Resource);
public static void CloseExcel(Worksheet activeSheet, Workbook wrkbook, Workbooks wrkbooks)
{
//http://support.microsoft.com/kb/317109 -> excel just wont close for some reason
if (activeSheet != null)
{
Marshal.FinalReleaseComObject(activeSheet);
activeSheet = null;
}
if (wrkbook != null)
{
wrkbook.Saved = true;
wrkbook.Close(Microsoft.Office.Interop.Excel.XlSaveAction.xlDoNotSaveChanges);
}
if (wrkbooks != null)
{
wrkbooks.Close();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
Sometimes Excel just wont close and you have to kill it (after trying to close it properly of course) - I dont recommend this, but if you cant track down the un-disposed memory and all else fails then...
if (xlApp != null)
{
ExcelDataSourceHelper.GetWindowThreadProcessId(new IntPtr(xlApp.Resource.Hwnd), ref excelProcessId);
}
if (excelProcessId > 0)
{
XlHelper.KillProcess(excelProcessId);
}
public static void KillProcess(int excelProcessId)
{
if (excelProcessId > 0)
{
System.Diagnostics.Process ExcelProc = null;
try
{
ExcelProc = System.Diagnostics.Process.GetProcessById(excelProcessId);
if (ExcelProc != null)
{
ExcelProc.Kill();
}
}
catch
{ }
}
}
Note: I reduce the chances of needing to kill Excel by using VSTO Contrib with Using's.
OK thank you all for trying to solve the issue
The solution was using Excel 2011/2013 Add-In which can communicate excel as a plugin
create an application-level add-in for Microsoft Office Excel. The features that you create in this kind of solution are available to the application itself, regardless of which workbooks are open.
You can visit MSDN

When Excel file is opened externally during C# application, an exception occurs

I wrote a C# application that queries a DB and puts the results in an excel file. The program itself works fine. However if I open a second completely unrelated excel file while the application is running, an exception occurs and the process stops.
Now, in the program itself I've set the visibility to false, and after opening the second unrelated Excel file, the one that was being generated is suddenly open and visible, and then I get the exception.
Anyone know what is causing the problem or how to resolve it?
The relevant parts of the code is as follows, the exception occurs at the line worksheet.get_Range(currCol.GetString() + excelRow, Missing.Value).Formula = item.ToString();
The exception itself is : "Exception from HRESULT: 0x800AC472"
Application exc = new Application();
//Makes the Excel file not visible
exc.Visible = false;
exc.UserControl = false;
exc.DisplayAlerts = false;
Workbooks workbooks = exc.Workbooks;
Workbook workbook = workbooks.Add(XlWBATemplate.xlWBATWorksheet);
Sheets sheets = workbook.Worksheets;
Worksheet worksheet = (Worksheet)sheets.get_Item(1);
int excelRow = 1;
ExcelChar currCol = new ExcelChar('A');
System.Data.DataTable testTable = dbConnection.searchQuery("Select * from testTable").Copy();
if (worksheet == null)
{
Console.WriteLine("ERROR: worksheet == null");
}
foreach (System.Data.DataRow row in testTable.Rows)
{
foreach (var item in row.ItemArray)
{
worksheet.get_Range(currCol.GetString() + excelRow, Missing.Value).Formula = item.ToString();
currCol.Add(1);
}
excelRow++;
currCol = new ExcelChar('A');
}
Take a look at this thread.
It looks like your error is VBA_E_IGNORE, in which case you need to register an IMessageFilter implementation so you can implement retry logic.
I've seen this issue in the past, when using the same instance of Excel with interop and interactively - for example when instatiating an Excel Application object using:
Marshal.GetActiveObject("Excel.Application")
In your case, you're creating a new instance of Excel using:
exc = new Application();
What you should try to do is to make sure you close this instance as quickly as possible. This is not always easy, because of the problem described in this KB article. Otherwise you might consider something other than COM Interop to write to Excel (e.g. OLEDB or a third party library such as Aspose or EPPlus).
When Excel is busy - e.g. has a modal dialog displayed, or is busy loading a workbook, it will not respond to incoming COM messages, so it returns an error which is translated into this exception. An IMessageFilter implementation (specifically: RetryRejectedCall) will typically retry a few times, then either fail or prompt the user to retry ("Server busy").
When using the Office InterOp services, you must close the objects you've created in reverse order.
private static void Excel_FromDataTable(DataTable dt)
{
// Global missing variable.
object missing = System.Reflection.Missing.Value;
// Creates an excel object,
Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
// Then a workbooks object,
Excel.Workbooks workbooks = excel.Workbooks;
// Then adds a workbook object,
Excel.Workbook workbook = workbooks.Add(true);
// Then adds a worksheet object,
Excel.Worksheet activeSheet = workbook.ActiveSheet;
// Then names the worksheet to what we need.
activeSheet.Name = "scbyext";
// Add column headings,
int iCol = 0;
// for each row of data,
int iRow = 0;
foreach (DataRow r in dt.Rows)
{
iRow++;
// Then add each row's cell data.
iCol = 0;
foreach (DataColumn c in dt.Columns)
{
iCol++;
excel.Cells[iRow, iCol] = r[c.ColumnName];
}
}
// Disable Excel prompts.
excel.DisplayAlerts = false;
// Save the workbook to the correct folder.
workbook.SaveAs("C:\\Escaped\\Path",
Excel.XlFileFormat.xlExcel8, missing, missing,
false, false, Excel.XlSaveAsAccessMode.xlNoChange,
missing, missing, missing, missing, missing);
// Release the objects we made, in reverse order, to allow Excel to quit correctly.
ReleaseObj(activeSheet);
ReleaseObj(workbook);
ReleaseObj(workbooks);
excel.Quit();
ReleaseObj(excel);
}
If you don't, the process will stay stuck open. I'm not sure what it's doing when it stays stuck, but by the end of the day the CPU Time used by it can get quite high.
I had a similar problem. I was creating an excel file using C# with a lot of charts that took a long time to create. If a user opened an existing excel file while my C# file was still being written it caused an exception to be thrown and my application would crash.
I fixed it with the following:
xlApp = new Application();
xlApp.IgnoreRemoteRequests = true;

Calculating User Defined Functions in Excel using C# Excel Interop

I am trying to an Add-in directly from C# so that when I open a workbook, and do a Workbook.Calculate() the UDF's (User Defined Functions) that are defined in an external addin correctly calculate in the worksheet. Currently, I am looping through each adding and simple setting:
AddIn.Installed = true
This does not work. C# does not load add-in at all, and I want to avoid using VBA. I want to open a workbook an excel workbook with the specific add in loaded, do a full calculated, and should have all values of the worksheet updated, including cells with UDF's.
Thanks for any help....
Some code:
Excel.Workbook wkbk = ExcelApp.ActiveWorkbook;
Excel.XlFixedFormatType paramExportFormat = Excel.XlFixedFormatType.xlTypePDF;
Excel.XlFixedFormatQuality paramExportQuality = Excel.XlFixedFormatQuality.xlQualityStandard;
bool paramOpenAfterPublish = false;
bool paramIncludeDocProps = true;
bool paramIgnorePrintAreas = true;
object paramFromPage = Type.Missing;
object paramToPage = Type.Missing;
ExcelApp.Visible = true;
//foreach (Excel.AddIn aiTemp in ExcelApp.AddIns)
//{
// if (aiTemp.Name.Contains(""))
// {
// aiTemp.Installed = false;
// aiTemp.Installed = true;
// }
//}
while (ExcelApp.CalculationState == Microsoft.Office.Interop.Excel.XlCalculationState.xlCalculating)
{
Thread.Sleep(50);
}
ExcelApp.CalculateFull();
var wksht = wkbk.ActiveSheet;
Excel.Range rng = ((Excel.Worksheet)wksht).get_Range("B1", "B1");
rng.Calculate();
//EnsureCalcFinished();
ExcelApp.Visible = false;
wkbk.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType.xlTypePDF, PathToDocument.Replace(".xlsx", ".pdf"), paramExportQuality, true, false, Type.Missing, Type.Missing, true,Type.Missing);
UPDATE:
I found a link with the method I use to register UDFs.
Ref: http://msdn.microsoft.com/en-us/library/ms173189(v=vs.80).aspx
In Excel, you need to go to Options -> Add-Ins => Excel Add-in (Go..) => automation = select the library and hit OK. Once you do this once, it'll be auto-loaded each time you open excel.
Here is the algorithm to load excel AddIns so that when you open excel workbook, a calculation will include UDF's defined in Add-in:
1)Initiliaze Excel Application
ExcelApp = new Excel.Application();
2)Load AddIns *
foreach (Excel.AddIn ai in ExcelApp.AddIns)
{
ai.Installed = false;
ai.Installed = true;
ExcelApp.Wait(50);
}
**The Key is to load add-ins before you open Excel Workbook.
3)Open Excel Workbook, which will trigger calculations
4)Set Calculation Mode to manual so that any changes in Interop do not trigger lengthy recalc
ExcelApp.Calculation = Microsoft.Office.Interop.Excel.XlCalculation.xlCalculationManual;
5)Perform any manipulations, and perform calc
ExcelApp.CalculateFull();
6)Dispose of Excel Objects appropriately
Hope this helps someone with a similar issue.. Ended up being a simple fix for a simple problem. Just remember to load add-ins before opening the workbook. Otherwise, opening an excel workbook with UDF's dependent on AddIn will fail.

Categories

Resources