writing data from C# to Excel interrupted by opening Excel Window - c#

While my C# program writes data continuously to an Excel spreadsheet, if the end user clicks on the upper right menu and opens the
Excel Options window, this causes following exception:
System.Runtime.InteropServices.COMException with HRESULT: 0x800AC472
This interrupts the data from being written to the spreadsheet.
Ideally, the user should be allowed to do this without causing an exception.
The only solution I found to this error code was to loop and wait until the exception went away:
Exception from HRESULT: 0x800AC472
which effectively hangs the app, data is not written to Excel and the user is left in the dark about the problem.
I thought about disabling the main menu of Excel while writing to it, but cannot find a reference on how to do this.
My app supports Excel 2000 to 2013.
Here is how to reproduce the issue:
Using Visual Studio Express 2013 for Windows Desktop, .NET 4.5.1 on Windows 7 64-bit with Excel 2007, create a new Visual C# Console Application project.
Add reference to "Microsoft ExceL 12.0 Object Library" (for Excel) and to "System.Windows.Forms" (for messagebox).
Here is the complete code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;
using System.Threading; // for sleep
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using Microsoft.Win32;
using Excel = Microsoft.Office.Interop.Excel;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int i = 3; // there is a split pane at row two
Excel.Application xlApp;
Excel.Workbook xlWorkBook;
Excel.Worksheet xlWorkSheet;
try
{
object misValue = System.Reflection.Missing.Value;
xlApp = new Excel.Application();
xlApp.Visible = false;
xlWorkBook = xlApp.Workbooks.Add(misValue);
xlApp.Visible = true;
xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
// next 2 lines for split pane in Excel:
xlWorkSheet.Application.ActiveWindow.SplitRow = 2;
xlWorkSheet.Application.ActiveWindow.FreezePanes = true;
xlWorkSheet.Cells[1, 1] = "Now open the";
xlWorkSheet.Cells[2, 1] = "Excel Options window";
}
catch (System.Runtime.InteropServices.COMException)
{
System.Windows.Forms.MessageBox.Show("Microsoft Excel does not seem to be installed on this computer any longer (although there are still registry entries for it). Please save to a .tem file. (1)");
return;
}
catch (Exception)
{
System.Windows.Forms.MessageBox.Show("Microsoft Excel does not seem to be installed on this computer any longer (although there are still registry entries for it). Please save to a .tem file. (2)");
return;
}
while(i < 65000)
{
i++;
try
{
xlWorkSheet.Cells[i, 1] = i.ToString();
Thread.Sleep(1000);
}
catch (System.Runtime.InteropServices.COMException)
{
System.Windows.Forms.MessageBox.Show("All right, what do I do here?");
}
catch (Exception)
{
System.Windows.Forms.MessageBox.Show("Something else happened.");
}
}
Console.ReadLine(); //Pause
}
}
}
Lanch the app, Excel appears and data is written to it. Open the Excel options dialog window from the menu and up pops the error:
An exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll and wasn't handled before a managed/native boundary
Additional information: Exception from HRESULT: 0x800AC472
Click on Continue and my message box "All right, what do I do here?" appears.
Please advise?
Best regards,
Bertrand

We finally went all the way to Microsoft Support with this issue. Their final response was:
I am able to reproduce the issue. I researched on this further and
found that this behaviour is expected and by design. This
exception, 0x800AC472 – VBA_E_IGNORE, is thrown because Excel is busy
and will not service any Object Model calls. Here is one of the
discussions that talks about this.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/9168f9f2-e5bc-4535-8d7d-4e374ab8ff09/hresult-800ac472-from-set-operations-in-excel?forum=vsto The work around I see is to explicitly catch this exception and retry
after sometime until your intended action is completed.
Since we cannot read the minds of the user who might decide to open a window or take a note without realizing the soft has stopped logging (if you mask the error), we decided to work around using:
xlWorkSheet.EnableSelection = Microsoft.Office.Interop.Excel.XlEnableSelection.xlNoSelection;
to lock the Excel window UI. We provide an obvious "unlock" button but when the user clicks it, he is sternly warned in a messagebox along with a "Do you wish to continue?"

Make Excel Interactive is a perfect solution. The only problem is if the user is doing something on Excel at the same time, like selecting range or editing a cell. And for example your code is returning from a different thread and trying to write on Excel the results of the calculations. So to avoid the issue my suggestions is:
private void x(string str)
{
while (this.Application.Interactive == true)
{
// If Excel is currently busy, try until go thru
SetAppInactive();
}
// now writing the data is protected from any user interaption
try
{
for (int i = 1; i < 2000; i++)
{
sh.Cells[i, 1].Value2 = str;
}
}
finally
{
// don't forget to turn it on again
this.Application.Interactive = true;
}
}
private void SetAppInactive()
{
try
{
this.Application.Interactive = false;
}
catch
{
}
}

xlApp = new Excel.Application();
xlApp.Interactive = false;

What I have done successfully is to make a temp copy of the target excel file before opening it in code.
That way I can manipulate it independent of the source document being open or not.

One possible alternative to automating Excel, and wrestling with its' perculiarities, is to write the file out using the OpenXmlWriter writer (DocumentFormat.OpenXml.OpenXmlWriter).
It's a little tricky but does handle sheets with > 1 million rows without breaking a sweat.
OpenXml docs on MSDN

Since Interop does cross threading, it may lead to accessing same object by multiple threads, leading to this exception, below code worked for me.
bool failed = false;
do
{
try
{
// Call goes here
failed = false;
}
catch (System.Runtime.InteropServices.COMException e)
{
failed = true;
}
System.Threading.Thread.Sleep(10);
} while (failed);

Related

SSIS Package fails when called from task scheduler via a batch file because of a script task

I have an SSIS package being called from a batch file and I am trying to schedule it via the task scheduler. The package works fine in Visual Studio, and it works when I execute the batch file, but it fails when I run the package through the scheduler. I've read all other post on this topic and I don't see anything relevant to mine, the problem is not configuration of the task scheduler properties (i.e the account it's using, run at highest privilege, start in directory, etc..).
I run multiple packages successfully through the task scheduler with no issues, this one just happens to use a c# script task that I had to add an assembly reference to and I think that's what is causing the problems when the package runs via the scheduler as the other packages use c# script task without issue but I did not add any assemblies.
This is the C# script which is used to format an excel spreadsheet after it's populated with data.
using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
#endregion
namespace ST_2bdf93d5542441248076f053703d32c9
{
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
public void Main()
{
int lastUsedColumn = 0;
string inputFile = (string)Dts.Variables["RecommendationFileName"].Value;
string RecommendationName = (string)Dts.Variables["RecommendationName"].Value;
Excel.Application ExcelApp = new Excel.Application();
Excel.Workbook ExcelWorkbook = ExcelApp.Workbooks.Open(inputFile);
//ExcelApp.Visible = true; //Use this to show the excel application/spreadsheet while the package is running. Not good for prod, just testing.
ExcelApp.Visible = false;
Excel.Worksheet xlWorkSheetFocus = (Excel.Worksheet)ExcelWorkbook.Worksheets.get_Item(3);
xlWorkSheetFocus.Activate();
xlWorkSheetFocus.Select(Type.Missing);
Excel.Range usedRange = xlWorkSheetFocus.UsedRange;
foreach (Excel.Worksheet ExcelWorksheet in ExcelWorkbook.Sheets)
{
ExcelWorksheet.Columns.AutoFit(); //Autofit the column to width for each worksheet, we adjust some column widths manually later.
if (ExcelWorksheet.Name == "Recommendations")
{
ExcelWorksheet.Cells[1, 4].EntireColumn.ColumnWidth = 125;
ExcelWorksheet.Cells[1, 4].EntireColumn.WrapText = true;
}
if (ExcelWorksheet.Name == "Passed")
{
ExcelWorksheet.Cells[1, 4].EntireColumn.ColumnWidth = 125;
ExcelWorksheet.Cells[1, 4].EntireColumn.WrapText = true;
}
if ((ExcelWorksheet.Name != "Recommendations") & (ExcelWorksheet.Name != "Passed"))
{
// Find the last real column in each worksheet
lastUsedColumn = ExcelWorksheet.Cells.Find("*", System.Reflection.Missing.Value,
System.Reflection.Missing.Value, System.Reflection.Missing.Value,
Excel.XlSearchOrder.xlByColumns, Excel.XlSearchDirection.xlPrevious,
false, System.Reflection.Missing.Value, System.Reflection.Missing.Value).Column;
ExcelWorksheet.Rows["1"].Insert(); //insert empty top row
ExcelWorksheet.Rows["2"].Insert(); //insert empty second row
ExcelWorksheet.Rows["3"].Insert(); //insert empty second row
ExcelWorksheet.Cells[1, 1].Interior.Color = 0x565656; //Row 1 = Dark Gray
ExcelWorksheet.Cells[2, 1].Interior.Color = 0x565656; //Row 2 = Dark Gray
ExcelWorksheet.Cells[3, 1].Interior.Color = 0x3ad7bd; //Row 3 = Green
ExcelWorksheet.Range[ExcelWorksheet.Cells[4, 1], ExcelWorksheet.Cells[4, lastUsedColumn]].Interior.Color = 0xCECECE; //Row 4 = Light Gray
//Bold the Fourth row of each spreadsheet (column headers are here)
ExcelWorksheet.Range["A4"].EntireRow.Font.Bold = true;
//Add a link back to the Recommendations page in row 2
ExcelWorksheet.Hyperlinks.Add(ExcelWorksheet.Cells[2, 1], "#Recommendations!A2", Type.Missing, "Return to Recommendations", "Return to Recommendations");
//Change row 1 to White, Bold, and 12pt font Arial, this is the report Title
ExcelWorksheet.Cells[1, 1].Font.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.White);
ExcelWorksheet.Cells[1, 1].Font.Bold = true;
ExcelWorksheet.Cells[1, 1].Font.size = 12;
ExcelWorksheet.Cells[1, 1].Font.Name = "Arial";
Excel.Range formatRange;
formatRange = ExcelWorksheet.get_Range("c1", "c1");
}
}
ExcelWorkbook.Save();
GC.Collect();
GC.WaitForPendingFinalizers();
ExcelWorkbook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(ExcelWorkbook);
ExcelApp.Quit();
Marshal.FinalReleaseComObject(ExcelApp);
}
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
}
}
And here are the references I added to this script task:
My question is, knowing that it has something to do with these references, does anyone understand why this happens? I am running the task with a local admin account and the batch file is on the local filesystem, everything else works in the package until this script task when using the task scheduler. I tries to copy the Excel Interop DLL file to the same folder as the batch file and re-added the reference to see if maybe that was the issue to no avail. My other script task which I did not have to add any assembly references to work just fine this way.
ding ding ding
I had to add an assembly reference to and I think that's what is causing the problems
Correct. You are using the Excel object model, via Microsoft.Office.Interop.Excel, to build/modify an Excel Workbook. The scheduler server does not have Office installed so the package fails as it can't find the required libraries. The correct resolution is to install Office on the server.
I tries (sic) to copy the Excel Interop DLL file to the same folder as the batch file
You do not want to "solve" the problem by copying the required assemblies to the scheduler. Even if you get all the required files installed, you've now opened your company up to failing an audit.
Office isn't free, the fine folks in Redmond built it, your organization will want to pay for it because paying upfront is so much cheaper than an audit finding a willful violation. Compare and contrast these conversations
"Oh yeah, we installed XYZ on this box an forgot about it" Auditors: ok, fine, true up your licensing and pay for what you're using. $
"Oh yeah, we mirrored on the libraries over there, installed them to the GAC, etc" Auditors: So it wasn't just an accident, that was deliberate and ignorance is not a defense. You owe us licensing fees and the following penalties. $$$
I came to realize that Interop was not going to work headless, either through the agent or task scheduler, so I switched to ClosedXML, built a console app, and execute it that way and it works.

c# Excel Application File Format issue

I have a small issues which i was not able to find any solution on StackOverFlow.
The Situation:
I have an application that automatically opens up EXCEL application. When it opens it right away gives a dialog box that says "The file format and extension of xxx don't match. The file could be corrupted or unsafe..."
I am trying to create an application that focus on this running EXCEL application and hit the "YES" and resave the excel with VERSION 2007 so this error message will not come up again.
Here is what i have so far:
var excelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
var c = excelApp.ActiveDialog;
Not sure how to do a click event on the YES using the excelApp variable.
I also tried through
foreach (Process proc in Process.GetProcesses())
{
if (proc.MainWindowTitle.Contains("Excel"))
....
}
Nov. 06 2017:
This is what i have so far now:
var oExcelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
Process[] processlist = Process.GetProcessesByName("Excel"); //Shows number of running Excel apps
foreach (Process theprocess in processlist) //foreach Excel app running
{
if (oExcelApp.Workbooks.Count >= 0) //for worbooks in each Excel app
{
foreach (Excel.Workbook wkb in oExcelApp.Application.Workbooks)
{
wkb.SaveAs(filePath, Excel.XlFileFormat.xlExcel8);
wkb.Close(true, null, null);
Marshal.FinalReleaseComObject(wkb);
}
oExcelApp.Workbooks.Close();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
oExcelApp.Quit();
Marshal.ReleaseComObject(oExcelApp);
}
This works ONLY If there was no error message at the beginning. If the process automatically opens excel and has error message showing this will throw an exception because the application is busy on the error message.
I don't know of a way to do exactly what you describe -- to intercept a dialog box within the application.
What I think you want to do is have Excel suspend warnings in the first place. You can do this by modifying the DisplayAlerts property to false.
var excelApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
excelApp.DisplayAlerts = false;
// This should not give you any warnings
excelApp.Workbooks.Open("c:/cdh/test.xls");
And when you are done, put things back as they were:
excelApp.DisplayAlerts = true;
hi you can create another application in which you can write logic of close alert box of Excel and execute from your main application
foreach (Process proc in Process.GetProcesses())
{
if (proc.MainWindowTitle.Contains("Excel"))
....
},
so no need to user interaction for close this alert box,
and you can save different alert message in database and check if this message come in alert box you can close
Thanks all for your help. I was able to go around this and disable all alerts for Microsoft Excel Application using the REGEDIT. This was the only way to go around this situation. Just posting an answer to help others in the same situation:
REGEDIT Solution (Read near the bottom)

Unable to set active Excel sheet C#

I am running some C# code as part of a script component running in my SSIS package. I am trying to open an Excel file and change the name of the sheet prior to importing the file in the next step of my SSIS package. I am getting an error on the line where I am trying to initialize "oSheet".
The error specifies: "Error 1 One or more types required to compile a dynamic expression cannot be found. Are you missing a reference? C:\Temp\Vsta\SSIS_ST110\VstaTP9LtckEMUWOXYp4Zy3YpQ\Vstau3xOw__Ey1kaOxXFoq0ff8g\ScriptMain.cs 107 26 ST_005c649f34584ed6873a7fde862ab2c9
"
I've not used C# for a while and was hoping someone could point me in the right direction. Thanks in advance!
Code:
public void Main()
{
String s = (String)Dts.Variables["FilePath"].Value;
String FileName = s.Substring(45,s.Length - 45); //45 = hardcoded value for known index of the start of the file name
MessageBox.Show(FileName);
Excel.Application oXL;
Excel._Workbook oWB;
Excel._Worksheet oSheet;
Excel.Range oRng;
try
{
oXL = new Microsoft.Office.Interop.Excel.Application();
oXL.Visible = false;
oWB = (Excel.Workbook)oXL.Workbooks.Open(s);
oSheet = (Excel._Worksheet)oWB.ActiveSheet;
//oSheet = (Excel._Worksheet)oXL.ActiveSheet;
//oSheet = (Excel._Worksheet)oWB.Worksheets.Item(0);
//oSheet = (Excel._Worksheet)oXL.Worksheets[FileName];
oSheet.Name = "NLTWNH";
oWB.Close(s);
}
catch (Exception ex)
{
//do nothing
}
Dts.TaskResult = (int)ScriptResults.Success;
}
First, add a reference to the Microsoft Excel Interop DLL. You do this by right clicking the References folder in the Solution Explorer. Then click Add Reference.
Click on the COM tab in the "Add Reference" window, and scroll down to your version of Excel's Object Library (I have chosen 15, but you may chose another version). Then click OK.
Now, it looks like your using statement should do something like this:
using Excel = Microsoft.Office.Interop.Excel;
Also, note that your oXL constructor can now just be
oXL = new Excel.Application();
I was missing a reference to "Microsoft.CSharp.dll" in my SSIS script task. To add the reference in Visual Studio 2012 click Project, Add Reference, then in the Framework tab scroll to find Miscrosoft.CSharp, check the corresponding box, and click OK.

Activate an Excel ComAddin from C# code

In fact, I have a C# application that is runned.
When it's launched, it runs an excel instance and save it to a variable :
excelApp = new Excel.Application();
Then I cycle through each of the excel's addins to find my own created added :
private void getAddin()
{
const string addinName = "myAddInName";
foreach (Office.COMAddIn addin in excelApp.COMAddIns)
if (addin.Description.ToUpper().Contains(addinName.ToUpper()))
{
myAddin = addin;
return;
}
}
The problem is that, it may happens that my plugin is in the deactivated elements list. (Due to some crash)
Then I must go to (on excel's frame) :
File >Options >AddIns >Manage >Deactivated elements >Achieve >"myAddin" >Activate
to get my addin working again (after excel's restart)...
I tried using an addin's property to reactivate it but may app crashes again in that case.
myAddin.Connect = loadAddin;
Someone has an idea on how to resolve it / auto-reactivate a disabled plugin using C# ?
This should help
http://msdn.microsoft.com/en-us/library/aa662931(v=office.11).aspx
I believe you need to do
if(!myAddin.Connect)
{
myAddin.Connect =true;
}

Unloading COM Objects C# [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to properly clean up Excel interop objects in C#
I am using EXCEL INTEROP for reading excel files in my .NET application. However I see that after am done with it, I still see the EXCEL.EXE in Windows Task Manager.
The code is as follows:
ApplicationClass excel = new ApplicationClass();
Workbooks workBooks = excel.Workbooks;
Workbook workBook = workBooks.Open(fileName,0,true,5,"","",true,XLPlatform.xlWindows,"\t",false,false,0,true,1,0);
foreach (Name name in workBook.Names)
{
try
{
// =#REF!#REF! indicates that the named range refers to nothing. Ignore these..
if (name.Value != "=#REF!#REF!")
{
if (!retNamedRanges.ContainsKey(name.Name))
{
string keyName = name.Name;
object value = name.RefersToRange.get_Value(missing);
retNamedRanges.Add(keyName, value);
}
}
}
catch (Exception ex)
{
}
}
if(workBook!=null)
{
workBook.Close(false,missing,missing);
}
if(workBook!=null)
{
workBooks.Close();
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBooks);
workBook = null;
workBooks = null;
excel.Application.Quit();
excel.Quit();
excel = null;
I have tried to do all possible things to clean up, but still it does not go. There are multiple EXCEL files that I need to read. Typically after my application executes I see multiple instances of EXCEL.EXE.
Is there anything else am missing with the clean up?
Many thanks in advance
"Some process specific to my application I am doing..."
Actually, this is most likely where the problem lies. As in the referenced duplicate, if you reference a property of the ApplicationClass then you'll need to make sure you dereference that property before the garbage collector will tidy up and remove Excel.
So, for instance, copy any data you need to string, int, etc. (or your own internal types based on these base types).
Try using Marshal.*Final*ReleaseComObject instead of ReleaseComObject.
Also call it on your "ApplicationClass excel" instance.

Categories