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.
Related
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.
I'm using the following code to run a VBA macro via C# Excel interop:
public void macroTest()
{
Excel.Application xlApp = new Excel.Application();
xlApp.Visible = true;
string bkPath = #"C:\somePath\someBk.xlsm";
Excel.Workbook bk = xlApp.Workbooks.Open(bkPath);
string bkName = bk.Name;
string macroName = "testThisMacro_m";
string runString = "'" + bkName + "'!"+macroName;
xlApp.Run(runString);
bk.Close(false);
xlApp.Quit();
}
testThisMacro_m is in a module testMacro, and this runs successfully. When I replace it with:
string macroName = "testThisMacro_s";
where testThisMacro_s has its code in Sheet1, the xlApp.Run() line gives the following COM Exception:
Cannot run the macro ''someBk.xlsm'!testThisMacro_s'.
The macro may not be available in this workbook or all macros may be disabled.
I checked macro security settings, and they are indeed set to "Disable with notification", but being able to run a macro from a module and not from a worksheet seems to indicate that this is a different issue than application-level macro security.
Is there something different that I have to do when making an interop call to a macro in a worksheet?
UPDATE: I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
but it seems that this hands control back to C# before the macro completes, so now I need to figure out how to check for macro completion (probably a different question).
A Worksheet object is an object - and objects are defined with class modules. Worksheets, workbooks, user forms; they're all objects. And you can't just call a method on an object, if you don't have an instance of that object.
Macros work off standard modules, which aren't objects, and don't need to be instantiated.
Application.Run can't call methods of an object, that's why macros need to be in standard modules.
I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
Wouldn't a helper sub solve both of your problems, re: Mat's Mug's reply concerning instantiation?
In some standard module:
Sub testHelperSubToBeCalledFromInterop
Call Sheet1.testThisMacro_s
End Sub
EDIT:
Similar questions have been asked before, but I want to try and refine it to my situation.
I am downloading a legacy file type (Excel 2003 (.xls)) and I need to strip the data from the file. The problem is I get :
{"Unable to cast COM object of type 'Microsoft.Office.Interop.Excel.ApplicationClass'
to interface type 'Microsoft.Office.Interop.Excel._Application'. This operation failed
because the QueryInterface call on the COM component for the interface
I am currently trying to get this working on my machine before I send the app to production, which will/does have excel on the server.
I have tried reinstalling office, but that did not work. I think the problem lies more in the fact that I have office 2013 on my box and I am attempting to run a decade old file type through it.
I tried to use this:
public void Convert(String file)
{
var app = new Application();
var wb = app.Workbooks.Open(file);
wb.SaveAs(file + "x", XlFileFormat.xlOpenXMLWorkbook);
wb.Close();
app.Quit();
}
That still causes the same problem, because the file will not open.
Any suggestions?
UPDATE
Here is the full method I am using
public Worksheet GetExcelBy(string url)
{
var fileName = #"C:\temp\tempfile.xls";
var webClient = new WebClient();
webClient.DownloadFile(url, fileName);
Convert(fileName);
var excel = new Application();
var workbook = excel.Workbooks.Open(fileName);
return (Worksheet)workbook.Worksheets["Data 6"];
}
this is the URL:
http://tonto.eia.doe.gov/dnav/pet/xls/pet_pri_spt_s1_d.xls
I presume the line with var excel = new Application(); is the line where the exception is raised. From my experience in the matter, as I also tried to follow other advice found on the internet, the solution I accidentally found was to explicitly define the non use of 32 bit access mode for the interop dll.
For doing so in visual studio, I had to tick and then untick the box "Prefer 32-bit" in the build section of the project configuration, which added <Prefer32Bit>false</Prefer32Bit> in the .csproj file.
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);
I'm using Microsoft.Office.Interop.Excel in a .Net Framework 4.5 Windows Service project on my 64bit Windows 7 desktop workstation and everything appears to be working except the PageSetup properties and more importantly the PrintOut method of the Worksheet object.
Here's the code:
Microsoft.Office.Interop.Excel.Application ExcelApp = null;
Microsoft.Office.Interop.Excel.Workbook WBook = new Microsoft.Office.Interop.Excel.Workbook;
Microsoft.Office.Interop.Excel.Worksheet WSheet = new Microsoft.Office.Interop.Excel.Worksheet);
ExcelApp = new Microsoft.Office.Interop.Excel.Application();
WBook = ExcelApp.Workbooks.Add();
WSheet = WBook.Worksheets[1];
//Do some stuff with the sheet
//None of this works, throws an error: Unable to set the Orientation property of the PageSetup class
WSheet.PageSetup.Orientation = Microsoft.Office.Interop.Excel.XlPageOrientation.xlLandscape;
WSheet.PageSetup.Zoom = false;
WSheet.PageSetup.FitToPagesWide = 1;
WSheet.PageSetup.FitToPagesTall = false;
//Throws Error (see below)
WSheet.PrintOut(null, null, null, null, null, null, null);
PrintOut() Error: No printers are installed. To install a printer click the File tab, and then click Print. Click No Printers Installed, and then click Add Printer. Follow the instructions in the Add Printer dialog box.
I definitely have printers defined in my Devices and Printers. I have a network printer set as my Default. I have even added this printer to Win.ini as I've seen this recommended in other posts but to no avail.
I figured this out while writing up the question.
Make sure you are running your Windows Service under an account that has printers set up and a specified Default printer. I was running the service under Local Service account, once I switched it to run under my personal account both the PageSetup properties and the PrintOut() method worked!