Excel does not close - c#

My problem is that I have a program that reads data from an Excel sheet .xlsb, but when the Excel file is open, then it asks me to save. Why?
async Task<bool> ReadVariable()
{
bool succeeded = false;
while (!succeeded)
{
//open file excel using microsoft dll
Excel.Application app = new Excel.Application();
//open workbook
Workbook wk = app.Workbooks.Open(excelpath, ReadOnly : true);
//get first sheet
Worksheet sh = wk.Worksheets[1];
//get cell
// Cells[unten/rechts] Example: [1,2] = B1
var day1tag = sh.Cells[27, 2].Value.ToString();
exceltest1.Text = day1tag;
var day1früh = sh.Cells[26, 2].Value.ToString();
Day24oee24.Text = day1früh;
app.DisplayAlerts = false;
wk.Close(SaveChanges : false);
app.Quit();
await Task.Delay(15000);
//await Task.Delay(108000000);
}
return succeeded;
}

[In addition to #JohnG]
First, you should put the app.Quit(); command line out side of the while loop and then do your algorthyms, after that save your workbook with this code;
xlWorkbook.SaveAs(saveFileDialog1.FileName + ".xlsx", Excel.XlFileFormat.xlWorkbookDefault, null, null, null, null, Excel.XlSaveAsAccessMode.xlExclusive, null, null, null, null, null);
and then use
app.Quit();
In addition;
After all process zombie excel will be shown on your task manager to solve that I would like to recommend as follow;
Import;
using System.Diagnostics;
To kill zombie excel use this function;
private void KillSpecificExcelFileProcess(string excelFileName)
{
var processes = from p in Process.GetProcessesByName("EXCEL")
select p;
foreach (var process in processes)
{
if (process.MainWindowTitle == excelFileName)
process.Kill();
}
}
And call the function as follow; (Interop excels are nameless due to we should use ("").
KillSpecificExcelFileProcess("");

Related

Excel doesn't close despite objects being released

So, I have a class that I wrote to handle opening excel files, however excel doesn't close even though I call the close function. This worked before I started using multiple workbooks so I presume that its something to do with that but I have no idea how to fix this. Any help appreciated!
This is the code:
public class ExcelReaderClass
{
public Excel.Application? excelApp = null; // create a null excell object that will later be used to store the reference to the open excel app
public Excel.Workbooks? excelBooks = null; // create a null object collection that will contain all workbooks
public Excel.Workbook? excelBook = null; // create a null object that will contain a workbook (excel file)
public Excel.Worksheet? curWorksheet = null; // create a null worksheet object that will contain the reference to the current worksheet that is open
public Excel.Range? curWorksheetRange = null; // create a range object that will contain the data from the currently open worksheet
// function that opens excel file and opens excel if it isnt already open
public void openExcelFile(string filePath) // filePath = filepath for the worksheet that is to be opened
{
if(excelApp == null) // if excel isnt open
{
excelApp = new Excel.Application(); // create a new instance of excel
excelApp.DisplayAlerts = false; // prevents "do you want to save" popups, which we want because we are only looking at files not changing them
excelBooks = excelApp.Workbooks; // create a new instance of workbook object collection
}
excelBook = excelBooks.Add(filePath); // add the workbook to the collection
}
// function that closes all instances of excel opened by this program
public void closeExcelFile(bool save, bool closeAll) // bool that controls whether only the worksheet is closed or the whole instance of excel
{
//excelBook.Save(); // save function not needed in this instance but might be useful later
GC.Collect();
GC.WaitForPendingFinalizers();
foreach (Excel.Workbook workbook in excelBooks)
{
workbook.Close(save, System.Reflection.Missing.Value, System.Reflection.Missing.Value); // close the workbook
NAR(workbook);
}
NAR(excelBooks); ; // release the workbook collection from Com
NAR(excelBook);
NAR(curWorksheet);
NAR(curWorksheetRange);
if (closeAll) // if close all is true, we want to close the excel instance too
{
excelApp.Quit(); // quit out of excel
NAR(excelApp); // release excel from Com
}
}
public List<object[,]> readExcelFile(string[] filePaths) //
{
List<object[,]> rangeList = new List<object[,]>(); // list of objects that will contain the data from the excel sheets
foreach (var filePath in filePaths) // for each file
{
openExcelFile(filePath); // open the file in excel
}
foreach (var workbook in excelBooks) // iterate through workbooks
{
var book = workbook as Excel.Workbook; // set book as a reference to the workbook
curWorksheet = book.Worksheets[1]; // set this variable to the first sheet in the workbook
curWorksheetRange = curWorksheet.UsedRange; // set this variable to the workbook range
object[,]? range = (object[,])curWorksheetRange.Value2;
rangeList.Add(range);
}
closeExcelFile(false,true);
return (rangeList);
}
//function to kill object and release it from COM
private void NAR(object o)
{
try
{
Marshal.FinalReleaseComObject(o);
}
catch { }
finally
{
o = null;
}
}
}
I've tried using GC.Collect() and GC.WaitForPendingFinalizers() but that doesn't seem to work either.
Update, this is what I did to fix the code. Feel free to use it. (Doesn't work in Debug mode though)
public class ExcelReaderClass
{
public Excel.Application? excelApp = null; // create a null excell object that will later be used to store the reference to the open excel app
public Excel.Workbooks? excelBooks = null; // create a null object collection that will contain all workbooks
public Excel.Workbook? excelBook = null; // create a null object that will contain a workbook (excel file)
public Excel.Worksheet? curWorksheet = null; // create a null worksheet object that will contain the reference to the current worksheet that is open
public Excel.Range? curWorksheetRange = null; // create a range object that will contain the data from the currently open worksheet
// function that opens excel file and opens excel if it isnt already open
public void openExcelFile(string filePath) // filePath = filepath for the worksheet that is to be opened
{
if(excelApp == null) // if excel isnt open
{
excelApp = new Excel.Application(); // create a new instance of excel
excelApp.DisplayAlerts = false; // prevents "do you want to save" popups, which we want because we are only looking at files not changing them
excelBooks = excelApp.Workbooks; // create a new instance of workbook object collection
}
excelBook = excelBooks.Add(filePath); // add the workbook to the collection
}
// function that closes all instances of excel opened by this program
public void closeExcelFile(bool save, bool closeAll) // bool that controls whether only the worksheet is closed or the whole instance of excel
{
foreach (Excel.Workbook workbook in excelBooks)
{
workbook.Close(save, System.Reflection.Missing.Value, System.Reflection.Missing.Value); // close the workbook
}
excelBooks = null; // clear the reference to the workbook collection
excelBook = null; // clear the reference to the active workbook
curWorksheet = null; // clear the reference to the current worksheet
curWorksheetRange = null; // clear the reference to the range of the current worksheet
if (closeAll) // if close all is true, we want to close the excel instance too
{
excelApp.Quit(); // quit out of excel
excelApp = null;
}
}
public List<object[,]> readExcelFile(string[] filePaths)
{
List<object[,]> rangeList = new List<object[,]>();
foreach (var filePath in filePaths)
{
openExcelFile(filePath);
}
Console.WriteLine();
foreach (var workbook in excelBooks) // done to select the first workbook because other methods have failed but this works
{
var book = workbook as Excel.Workbook; // set book as a reference to the workbook
curWorksheet = book.Worksheets[1]; // set this variable to the first sheet in the workbook
curWorksheetRange = curWorksheet.UsedRange; // set this variable to the workbook range
object[,]? range = (object[,])curWorksheetRange.Value2;
rangeList.Add(range);
}
closeExcelFile(false,true);
GC.Collect();
GC.WaitForPendingFinalizers();
return (rangeList);
}
}
Quick Rundown:
I deleted the NAR function and any places it was called, added GC.Collect() and GC.WaitForPendingFinalisers after all my excel code is executed, and made sure all variables are set to null before GC.* functions are called.

c# after client runs my program the excel sheet the program writes always opens when excel is opened

I have created a C# program that takes a XML document as input, drag and drop on exe. The output is an xlsx sheet i create using the input.
When I give the exe to a coworker to run, it works as expected, and outputs the xlsx. But now after the coworker closes out of excel and opens any excel workbook any where on the computer, the xlsx that was created from my program always opens.
This doesn't happen on my computer. Not sure how it's even possible. Is there some setting I am missing in saving the files?
public static void WriteFile2(string filePath, InitialData initialData)
{
var excelApp = new Application {Visible = false};
var workbooks = excelApp.Workbooks;
_Workbook workbook = workbooks.Add(XlWBATemplate.xlWBATWorksheet);
var sheets = workbook.Worksheets;
var worksheet = (_Worksheet) sheets.Item[1];
worksheet.Name = "BGPM1000XX";
excelApp.StandardFont = "Arial";
excelApp.StandardFontSize = 8;
/*Skipping body of code, mostly writing to cells and formatting*/
//Insert Cover sheet tab
var newWorksheet = (Worksheet) excelApp.Worksheets.Add();
newWorksheet.Name = "COVER SHEET";
/*Skipping body of code, mostly writing to cells and formatting*/
try
{
//TODO: make png path generic
#if DEBUG
var fileName = #"..\..\..\ExternalReferences\logo.png";
var newname = Path.GetFullPath(fileName);
newWorksheet.Shapes.AddPicture(
newname,
MsoTriState.msoFalse, MsoTriState.msoCTrue, 280, 5, 55, 45);
#else
newWorksheet.Shapes.AddPicture(
#"logo.png",
MsoTriState.msoFalse, MsoTriState.msoCTrue, 280, 5, 55, 45);
#endif
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
workbook.Saved = true;
var version = 1;
var inUse = true;
var extension = Path.GetExtension(filePath);
while (inUse)
{
inUse = IsFileInUse(filePath);
if (!inUse)
break;
filePath = filePath.Substring(0, filePath.Length - extension.Length) + "_V" + version + ".xlsx";
version++;
}
excelApp.DisplayAlerts = false;
//workbook.SaveAs(filePath);
workbook.SaveAs(filePath, XlFileFormat.xlWorkbookDefault, Type.Missing, Type.Missing, false, false, XlSaveAsAccessMode.xlNoChange, XlSaveConflictResolution.xlLocalSessionChanges, Type.Missing, Type.Missing);
excelApp.UserControl = false;
excelApp.Quit();
}
make sure you kill the process if there is an exception in your code. A good way to handle it is with a try..catch block. After you call the exception, call something like this:
public void KillProcess()
{
_workBook.Close();
_workBooks.Close();
_excelApp.Quit();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.FinalReleaseComObject(_workSheets);
Marshal.FinalReleaseComObject(_workBook);
Marshal.FinalReleaseComObject(_workBooks);
Marshal.FinalReleaseComObject(_excelApp);
}
Also, to clean up all of your previous EXCEL tasks, restart your computer.
Two things
Put in a call to Close the document before you call Quit.
Place the whole Excel code into a try catch and have this command as a finally to release the com object.
System.Runtime.InteropServices.Marshal.ReleaseComObject( excelApp );
I base this advice off of my blog article C# Open Word Documents using Visual Studio 2010 and .Net 4 which provides an example of working with the interops.

Interop Excel not closing process

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.
Try
Process excelProcess = Process.GetProcessesByName("EXCEL")[0];
if (!excelProcess.CloseMainWindow())
{
excelProcess.Kill();
}
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();
}
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();
}
}
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);
}
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.
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
Simple rule: avoid using double-dot-calling expressions, such as this:
var workbook = excel.Workbooks.Open(/*params*/)
(Reference)
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);

Getting excel application process id

I am creating an excel application with c#.
Since I will maintain the excel file in urgency I want to keep its handler open.
I want to keep the excel process id so I will be able to kill it in case the system crashs.
How can I get the Excel Pid when creating it?
using Excel = Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices;
using System.Diagnostics;
class Sample
{
[DllImport("user32.dll")]
static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
Process GetExcelProcess(Excel.Application excelApp)
{
int id;
GetWindowThreadProcessId(excelApp.Hwnd, out id);
return Process.GetProcessById(id);
}
}
Here's an example of how to open an Excel file using Excel-Interop, and properly disposing the instance
(source: google)
Application ExcelObj = new Application();
Workbook WB = ExcelObj.Workbooks.Open(fileName,
0, true, 5, "", "", true, XlPlatform.xlWindows, "\t",
false, false, 0, true, false, false);
Sheets sheets = WB.Worksheets;
Worksheet WS = (Worksheet)sheets.get_Item(1);
Range excelRange = WS.UsedRange;
... (DO STUFF?)
// Get rid of everything - close Excel
while (Marshal.ReleaseComObject(WB) > 0) { }
WB = null;
while (Marshal.ReleaseComObject(sheets) > 0) { }
sheets = null;
while (Marshal.ReleaseComObject(WS) > 0) { }
WS = null;
while (Marshal.ReleaseComObject(excelRange) > 0) { }
excelRange = null;
GC();
ExcelObj.Quit();
while (Marshal.ReleaseComObject(ExcelObj) > 0) { }
ExcelObj = null;
GC();
public static void GC()
{
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}

How to access an already opened Excel file in C#?

I have an excel workbook opened via double-clicking it in windows explorer but cannot access it in code
Excel.Application xlApp = (Application)Marshal.GetActiveObject("Excel.Application");
Excel.Workbooks xlBooks = xlApp.Workbooks;
xlBooks.Count equals 0, why isn't it referencing my opened workbook?
EDIT
Here are the various scenarios and what is happening:
Scenario 1: If the file is not already open
Code opens workbook, I am happy.
Scenario 2: If the file is initially opened from code and I close and reopen the app
Code references file just fine xlBooks.Count equals 1, I am happy.
Scenario 3: If the file is initially opened not from code, and via double-clicking it in explorer
Code opens another instance of the file xlBooks.Count equals 0, I am in a rage!
Here is the entire code as it stands right now
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;
public class ExcelService : IExcelService
{
const string _filePath = #"C:\Somewhere";
const string _fileName = #"TestFile.xlsb";
string _fileNameAndPath = Path.Combine(_filePath, _fileName);
Application xlApp;
Workbooks xlBooks;
Workbook xlBook;
Worksheet xlSheet;
public ExcelService()
{
try
{
xlApp = (Application)Marshal.GetActiveObject("Excel.Application");
xlBooks = xlApp.Workbooks;
var numBooks = xlBooks.Count;
Log.Info("Number of workbooks: {0}".FormatWith(numBooks));
if (numBooks > 0)
{
xlBook = xlBooks[1];
Log.Info("Using already opened workbook");
}
else
{
xlBook = xlBooks.Open(_fileNameAndPath);
Log.Info("Opening workbook: {0}".FormatWith(_fileNameAndPath));
}
xlSheet = (Worksheet)xlBook.Worksheets[1];
// test reading a named range
string value = xlSheet.Range["TEST"].Value.ToString();
Log.Info(#"TEST: {0}".FormatWith(value));
xlApp.Visible = true;
}
catch (Exception e)
{
Log.Error(e.Message);
}
}
~ExcelService()
{
GC.Collect();
GC.WaitForPendingFinalizers();
try
{
Marshal.FinalReleaseComObject(xlSheet);
}
catch { }
try
{
Marshal.FinalReleaseComObject(xlBook);
}
catch { }
try
{
Marshal.FinalReleaseComObject(xlBooks);
}
catch { }
try
{
Marshal.FinalReleaseComObject(xlApp);
}
catch { }
}
}
I know this thread is a little old, but I found another way of doing this. When you create a new Excel.Applicationobject you have to create the WorkBooks object. When you access an already opened Excel file, the WorkBooks object is already created, so you just need to add a new WorkBook to the existing one. #Tipx 's solution works great if you have access to the WorkBook name, but in my case the current WorkBook name is always random. Here's the solution I came up with to get around this:
Excel.Application excelApp = null;
Excel.Workbooks wkbks = null;
Excel.Workbook wkbk = null;
bool wasFoundRunning = false;
Excel.Application tApp = null;
//Checks to see if excel is opened
try
{
tApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
wasFoundRunning = true;
}
catch (Exception)//Excel not open
{
wasFoundRunning = false;
}
finally
{
if (true == wasFoundRunning)
{
excelApp = tApp;
wkbk = excelApp.Workbooks.Add(Type.Missing);
}
else
{
excelApp = new Excel.Application();
wkbks = excelApp.Workbooks;
wkbk = wkbks.Add(Type.Missing);
}
//Release the temp if in use
if (null != tApp) { Marshal.FinalReleaseComObject(tApp); }
tApp = null;
}
//Initialize the sheets in the new workbook
Might not be the best solution but it worked for my needs. Hope this helps someone. :)
If all your workbooks are opened in the same Excel instance (you can check this by checking if you can switch from one to the other using Alt-tab). You can simply refer to the other using Workbooks("[FileName]"). So, for example :
Dim wb as Workbook //for C#, type Excel.Workbook wb = null;
Set wb = Workbooks("MyDuperWorkbook.xlsx") //for C#, type wb = Excel.Workbooks["MyDuperWorkbook.xlsx"];
wb.Sheets(1).Cells(1,1).Value = "Wahou!"

Categories