Interop Excel not closing process - c#

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);

Related

Excel does not close

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("");

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.

Create a new excel vs appending to existing one

I want to create an excel file for each day I run a Test, in D drive.
However, if the file already exists - as in I already ran a test for that day - I want it to append to the file.
This is what I got so far but I feel like it doesn't look right.
Any help would be great, Thanks a lot guys.
Excel.Application excelApp = new Excel.Application();
string date = DateTime.Now.ToString("MMM dd-yy");
string myPath = #"D:\" + date + ".xls";
int rowIndex = 1; int colIndex = 1;
Excel._Workbook oWB;
try
{
oWB = (Excel._Workbook)(excelApp.Workbooks.Open(myPath));
excelApp.Visible = false;
excelApp.Cells[rowIndex, colIndex] = "IN TRY METHOD";
oWB.Save();
oWB.Close();
}
catch (Exception e)
{
oWB = (Excel._Workbook)(excelApp.Workbooks.Add(System.Reflection.Missing.Value));
excelApp.Visible = false;
excelApp.Cells[rowIndex, colIndex] = "IN CATCH METHOD";
oWB.SaveAs(myPath);
oWB.Close();
}
Also I noticed that when I open the file it says:
The file you are trying to open is in a different format than
specified by file extension. Verify it's from a trusted source before
opening the file. Do you want to open now?
It works but it has this message before hand.
So please tell me what I'm doing wrong and also how to make this code a lot cleaner.
Check if the file is on disk and then take appropriate action instead of abusing try{}catch{}
if(File.Exists(myPath))
{
ModifyExcel();
}
else
{
CreateExcel();
}
private void ModifyExcel()
{
oWB = (Excel._Workbook)(excelApp.Workbooks.Open(myPath));
excelApp.Visible = false;
excelApp.Cells[rowIndex, colIndex] = "MODIFY";
oWB.Save();
oWB.Close();
}
private void CreateExcel()
{
oWB = (Excel._Workbook)(excelApp.Workbooks.Add(System.Reflection.Missing.Value));
excelApp.Visible = false;
excelApp.Cells[rowIndex, colIndex] = "CREATE";
oWB.SaveAs(myPath);
oWB.Close();
}
Instead of using a try .. catch this way you should first test to see if the file exists. If it does, then do your append code. If it doesn't then do the workbook creation code.
The reason you get that message is that you are using the newer Excel COM objects to create the file. Instead of emitting .xls as the extension try .xlsx

Copy specific worksheets to new Excel file

I have an Excel file.
I need to open it, select specific sheets from it, and convert those sheets to a PDF format. I am able to convert the whole excel file, I just don't know how to convert only the specific sheets.
My idea is to copy specific sheets from an existing file to a new temporary file, and convert that whole new temporary file to PDF.
Maybe there's an easier way?
My code so far is =>
using Word = Microsoft.Office.Interop.Word;
using Excel = Microsoft.Office.Interop.Excel;
public static void ExportExcel(string infile, string outfile, int[] worksheets)
{
Excel.Application excelApp = null;
Excel.Application newExcelApp = null;
try
{
excelApp = new Excel.Application();
excelApp.Workbooks.Open(infile);
//((Microsoft.Office.Interop.Excel._Worksheet)excelApp.ActiveSheet).PageSetup.Orientation = Microsoft.Office.Interop.Excel.XlPageOrientation.xlLandscape;
excelApp.ActiveWorkbook.ExportAsFixedFormat(Excel.XlFixedFormatType.xlTypePDF, outfile);
}
finally
{
if (excelApp != null)
{
excelApp.DisplayAlerts = false;
excelApp.SaveWorkspace();
excelApp.Quit();
}
}
}
Maybe the ExportAsFixedFormat method can be set to consider only specific pages (sheets) while converting?
If not, how do I copy the sheets from one file to another?
Thanks!
You might be able to just print the sheets you want from the original file. I fired up the Macro Recorder, selected a couple of sheets, and Saved As to PDF. Here's the code:
Sheets(Array("Sheet1", "Sheet2")).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:= _
"C:\Users\doug\Documents\Book1.pdf", Quality:=xlQualityStandard, _
IncludeDocProperties:=True, IgnorePrintAreas:=False, OpenAfterPublish:=True
When I changed the selected sheets and ran again it worked as expected.
What's strange is that in the actual Save As dialog, you can go to Options and check "Selected Sheets." That's not available as a parameter to ExportAsFixedFormat, but it was automatically selected in the dialog, and maybe is the default also when called.
You could simply copy the file to the new destination, open the destination, remove the unwanted sheets and export. This is an example (tested) of my idea.
// infile is the excel file, outfile is the pdf to build, sheetToExport is the name of the sheet
public static void ExportExcel(string infile, string outfile, string sheetToExport)
{
Microsoft.Office.Interop.Excel.Application excelApp = new
Microsoft.Office.Interop.Excel.Application();
try
{
string tempFile = Path.ChangeExtension(outfile, "XLS");
File.Copy(infile, tempFile, true);
Microsoft.Office.Interop.Excel._Workbook excelWorkbook =
excelApp.Workbooks.Open(tempFile);
for(int x = excelApp.Sheets.Count; x > 0; x--)
{
_Worksheet sheet = (_Worksheet)excelApp.Sheets[x];
if(sheet != null && sheet.Name != sheetToExport)
sheet.Delete();
}
excelApp.ActiveWorkbook.ExportAsFixedFormat(XlFixedFormatType.xlTypePDF, outfile);
}
finally
{
if (excelApp != null)
{
excelApp.DisplayAlerts = false;
excelApp.SaveWorkspace();
excelApp.Quit();
}
}
}

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