I am trying to cycle through a excel workbook and copy paste values over the top
of each sheet within that workbook. But I am running into a memory issue on line:
ws.select(true), when going to the next sheet.
Errors encountered:
Exception from HRESULT: 0x800A03EC:
and when I go to close out of the workbook at this point excel throws:
Out of memory (Error 7)
Additional information:
File size 3mb, 20 tabs and lots of database formulae reading from olap database TM1.
Microsoft office 2007
The code I am running is below. Is there a more efficient way of running it that may prevent the Out of Memory Error OR is this something different??
Any help would be much appreciated!
#
public bool wbCopyValueOnly(string workBook)
{
Workbook wb = excel.ActiveWorkbook;
Worksheet ws;
Range rng;
int WS_Count = wb.Sheets.Count;
for (int i = 0; i < WS_Count; i++)
{
try
{
ws = wb.Sheets[i + 1];
ws.Activate();
ws.Select(true);
//ws.Select(Type.Missing);
//ws.Cells.Select();
ws.UsedRange.Select();
//ws.Cells.Copy();
ws.UsedRange.Copy(Type.Missing);
ws.UsedRange.PasteSpecial(Excel.XlPasteType.xlPasteValues, Excel.XlPasteSpecialOperation.xlPasteSpecialOperationNone, false, false);
// select of range to get around memory issue
excel.Application.CutCopyMode = (Excel.XlCutCopyMode)0;
//rng = ws.get_Range("A1");
//rng.Select();
NAR(ws);
}
catch (System.Runtime.InteropServices.COMException err)
{
cLogging.write(LogLevel.Error, err.Message);
Debug.Print(err.Message);
return false;
}
}
NAR(wb);
return true;
}
private void NAR(object o)
{
try
{
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(o) > 0) ;
}
catch { }
finally
{
o = null;
}
}
I have a program that copies from up to 100 tabs back to the first summary page and I found that using .Copy() caused several problems. Especially if your program runs for a length of time; the current user cannot use the copy-paste function without strange results. I recommend using variables to store what you need and then write to the intended range. Recording Macros is invaluable if you need to change the format of the range.
ws.Activate();
ws.Select(true);
ws.UsedRange.Select();
I think these code isn't necessary.
You can record a Marco to learn how to modify your code.
Related
I have a Windows form app project (C#).
I am trying unsuccessfully to write code so that when I click a button I created, it will load an excel file that actually has macros in it and delete them all.
I know you can delete macros manually in the excel file itself but I need a way to do it programmatically (I just delete them all for the user).
I know that the macros in Excel files are written in VBA language, so I try to use the related libraries in c# but I get an error when I try to use VBProject, VBComponent.
This what I tried so far:
using Microsoft.Office.Interop.Excel;
namespace MacroRemover
{
public partial class Main : Form
{
private void Btn_Click(object sender, EventArgs e)
{
string filePath = "path\\to\\file.xlsm";
Application excel = new Application();
Workbook workbook = excel.Workbooks.Open(filePath);
VBProject vbProject = workbook.VBProject;
foreach (VBComponent component in vbProject.VBComponents)
{
vbProject.VBComponents.Remove(component);
}
workbook.Save();
workbook.Close();
excel.Quit();
}
}
}
Any way that works will help me, I would appreciate the help
Thanks in advance guys!!!
If you don't need to save xlsm format of the file, you can save this file to xlsx format and all scripts will be removed.
using Aspose.Cells;
var workbook = new Workbook("input.xlsm");
workbook.Save("Output.xlsx");
Thanks for all the replies, #tttony's response helped me to understand more deeply and #Bushuev's response here is definitely a possible and simple solution for deleting the macros.
I finally managed to delete all the macros like this:
string filePath = Path_TxtBox.Text;
Microsoft.Office.Interop.Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
Workbook workbook = excel.Workbooks.Open(filePath);
VBProject project = workbook.VBProject;
for (int i = project.VBComponents.Count; i >= 1; i--)
{
VBComponent component = project.VBComponents.Item(i);
try
{
project.VBComponents.Remove(component);
}
catch (ArgumentException)
{
continue;
}
}
for (int i = project.VBComponents.Count; i >= 1; i--)
{
VBComponent component = project.VBComponents.Item(i);
component.CodeModule.DeleteLines(1, component.CodeModule.CountOfLines);
}
workbook.Save();
workbook.Close();
excel.Quit();
MessageBox.Show("Macros Removed");
It should only be noted that I encountered an error: 'Programmatic access to Visual Basic Project is not trusted'
It was solved after I realized that I had to change the option to access the VBA Project object model in the Trust Center settings.
So basically my app triggers an excel macro, from a file, that updates the file and then closes it.
When I open the file I set the "DisplayAlerts = false" variable in order to ignore all popups and it works as expected in my computer... however, a colleague of mine tried to use it and for every file, he gets the popup asking if he wants to save all changes...
Checked other questions about the popups in excel but all suggested solutions use "oBook.Saved = true;" or "oBook.Close(false);", but these did not work for me.
my code is as follows:
using Microsoft.Office.Interop.Excel;
public static bool Trigger_Macro_From_File(string path)
{
ApplicationClass oExcel = null;
Workbook oBook = null;
try
{
string filename = Path.GetFileName(path);
string macro_name = "!some_macro";
string macro = #"'" + filename + #"'" + macro_name;
// Create an instance of Microsoft Excel
oExcel = new ApplicationClass
{
DisplayAlerts = false,
Visible = false
};
oBook = oExcel.Workbooks.Open(path);
RunMacro(oExcel, new Object[] { macro });
oBook.Save();
oBook.Saved = true;
return true;
}
catch (Exception ex)
{
return false;
}
finally
{
oBook?.Close(false);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oBook);
oBook = null;
oExcel?.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(oExcel);
oExcel = null;
GC.Collect();
}
}
Does anyone know anything about this?
Thanks in advance.
You could double-check that no other "Microsoft Excel" process is running in the Task Manager.
Let's say at some point in your development process you started your program and open the workbook with something like
xlWorkbook = xlApp.Workbooks.Open(filePath);
Then you encountered an exception for some reason, and killed the program without closing the file properly (workbook.Close(..), app.Quit(..) and so on).
The Microsoft Excel process is still running in the background, and has a handle on the file you want to edit. So you cannot execute an instruction that saves the file under the same name. This is why the popup is appearing.
This scenario is taken from the point of view of the developer, but the same behavior could have happened on your coworker's computer if your app crashed without quitting properly, and gets re-started.
Also, be careful that finally statement might not always be executed, so double-check which scenario could cause your app to close without releasing the COM object.
I have a small application that queries a DB on a networked Windows Server running SQL Server and outputs the data to an Excel file. I am working on formatting the excel file how I need and I'm running into an exception I've never seen when trying to view the data in the cells. Here is my code:
xlWorkSheet.Activate();
xlWorkSheet.Application.ActiveWindow.ScrollRow = 1;
xlWorkSheet.Application.ActiveWindow.SplitRow = 1;
xlWorkSheet.Application.ActiveWindow.FreezePanes = true;
xlWorkSheet.Cells[1, 1].EntireRow.Font.Bold = true;
xlWorkSheet.Columns.AutoFit();
for (i = 0; i <= xlWorkSheet.Rows.Count; i++)
{
if(xlWorkSheet.Cells[i,5].Value2 == "PAST DUE")
{
xlWorkSheet.Cells[i, 5].Interior.Color = Excel.XlRgbColor.rgbRed;
}
}
This is the exception I'm receiving:
System.Runtime.InteropServices.COMException: 'Exception from HRESULT: 0x800A03EC'
All other applications I've written that look at cell data have never had any problem with this same type of usage.
The Exception happens at this line:
if(xlWorkSheet.Cells[i,5].Value2 == "PAST DUE")
I) Global Explanation
Hello,
I'm developing an app in C# using Microsoft.Office.Interop.Excel. In my case I implement some olds macros stored in a file to an Excel Workbook, opened by C#, to upgrade them without re-coding macros in my app.
Globally there is my process in my app :
The user select an updated macro,
I check if the macro require something :
Here I set the register Key CURRENT_USER\SOFTWARE\Microsoft\Office\[office_ID].0\Excel\Security\AccessVBOM to 1 for allows macro/VBProject modifications
I instantiate an Excel app with a new workbook
I set optimization Excel execution parameters (disable refresh... that kind of stuff)
I add my macro, stored in a file, to the workbook
I execute the macro through Excel
I remove the macro from the Excel
I unset optimizations Excel executions parameters
I reset registers keys (.../AccessVBOM = 0)
II) My problem and my research result
In the "I add my macro, stored in a file, to the workbook" part, when I used the following instruction to add a VBProject to the workbook (required to add the macro) I get an error 800A03EC.
/// wb is a workbook object
VBComponent module = wb.VBProject.VBComponents.Add(vbext_ComponentType.vbext_ct_StdModule);
This error is send with a message (grossly traduce) : "The access to VB Project are not trust."
As I see on the Internet, this error occurs when an option in Excel isn't correctly set. This option can by handle with the CURRENT_USER\SOFTWARE\Microsoft\Office\[office_ID].0\Excel\Security\AccessVBOM register key.
And I'm building my app with a register key management for avoid this problem.
And as you will see on the debug display part, we can see the option is correctly set on the execution.
My question : there is another case when this error occurs?
III) Debug analyse
When I catch the ComException, I use the following to display registers keys values.
/// RequirementManager is my own class, use to set and unset endpoints computers parameters, before and after each user action
RegistryKey reg;
for (int i = RequirementManager.EXCEL_ID_HANDLED_MIN; i < RequirementManager.EXCEL_ID_HANDLED_MAX; i++)
{
reg = Registry.CurrentUser.CreateSubKey(#"SOFTWARE\Microsoft\Office\" + i + #".0\Excel\Security\", RegistryKeyPermissionCheck.ReadSubTree);
Debug.WriteLine(String.Format(#"Value of CURRENT_USER\SOFTWARE\Microsoft\Office\{0}.0\Excel\Security\AccessVBOM = {1}", i, reg.GetValue("AccessVBOM")), "Debug");
reg.Close();
}
reg = null;
And here is the console output of this lines :
Debug: Value of CURRENT_USER\SOFTWARE\Microsoft\Office\11.0\Excel\Security\AccessVBOM = 1
Debug: Value of CURRENT_USER\SOFTWARE\Microsoft\Office\12.0\Excel\Security\AccessVBOM = 1
Debug: Value of CURRENT_USER\SOFTWARE\Microsoft\Office\13.0\Excel\Security\AccessVBOM = 1
Debug: Value of CURRENT_USER\SOFTWARE\Microsoft\Office\14.0\Excel\Security\AccessVBOM = 1
Debug: Value of CURRENT_USER\SOFTWARE\Microsoft\Office\15.0\Excel\Security\AccessVBOM = 1
Debug: Value of CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\Excel\Security\AccessVBOM = 1
As you can see, the option is correctly set when the error occurs. And all instance of Excel are closed before the modification of register keys, for avoid problem. I use similar algorithm to modify this range of register keys, like a lazy boy.
Any idea are welcome
Regards, Dykoine
IV) Answer Reaction
#prizm1 This parameter if macro are allowed or not (display orange warning message), and on my computer (while test) I set it to allow all macro.
After test, I can see this option doesn't protect the user, because we can open an Excel file with C# and execute a macro in it without be block by this option even if you use "disabled all without notification".
Here the code which haven't made any problems with putting VBA into an Excel file. Maybe you need to save it to disk first before entering the Code, since the Project is determined by the filename. Just an addition, and no real solution:
public bool injectVBA(String scriptText, String target)
{
VBProject found = null;
Access.Application currApplication = this.currentInstance.Application;
if (target.Equals("") || scriptText.Equals(""))
return false;
foreach (VBProject vb in currApplication.VBE.VBProjects)
{
if (currApplication.CurrentDb().Name.Equals(vb.FileName))
{
found = vb;
break;
}
}
if (found != null)
{
foreach (VBComponent foundComponent in found.VBComponents)
{
if (foundComponent.Name.Equals(target))
{
return true;
}
}
VBComponent module = found.VBComponents.Add(vbext_ComponentType.vbext_ct_StdModule);
module.Name = target;
module.CodeModule.AddFromString(scriptText);
module.Activate();
//currApplication.DoCmd.OpenModule(target, Type.Missing);
currApplication.DoCmd.Save(Access.AcObjectType.acModule, target);
return true;
}
else
{
return false;
}
}
This is a C#/VSTO program. I've been working on a data capture project. The scope is basically 'process Excel files sent by a variety of third party companies.' Practically, this mean:
Locate columns that contain the data I want through a search method.
Grab data out of the workbooks
Clean the data, run some calculations, etc
Output cleaned data into new workbook
The program I have written works great for small-medium data sets, ~25 workbooks with a combined total of ~1000 rows of relavent data. I'm grabbing 7 columns with of data out of these workbooks. One edge case I have, though, is occasionally I will need to run a much larger data set, ~50 workbooks with a combined total of ~8,000 rows of relavent data (and possibly another ~2000 of duplicate data that I also have to remove).
I am currently putting a list of the files through a Parallel.ForEach loop inside of which I open a new Excel.Application() to process each file with multiple ActiveSheets. The parallel process runs much faster on the smaller data set than going through each one sequentially. But on the larger data set, I seem to hit a wall.
I start getting the message: Microsoft Excel is waiting for another application to complete an OLE action and eventually it just fails. Switching back to sequential foreach does allow the program to finish, but it just grinds along - going from 1-3 minutes for a Parallel medium sized data set to 20+ minutes for a sequential large data set. If I mess with ParallelOptions.MaxDegreeOfParallelism set to 10 it will complete the cycle, but still take 15 minutes. If I set it to 15, it fails. I also really don't like messing with TPL settings if I don't have to. I've also tried inserting a Thread.Sleep to just manually slow things down, but that only made the failure happen further out.
I close the workbook, quit the application, then ReleaseComObject to the Excel object and GC.Collect and GC.WaitForPendingFinalizers at the end of each loop.
My ideas at the moment are:
Split the list in half and run them seperately
Open some number of new Excel.Application() in parallel, but run a list of files sequentially inside of that Excel instance (so kinda like #1, but using a different path)
Seperate the list by file size, and run a small set of very large files independently/sequentially, run the rest as I have been
Things I am hoping to get some help with:
Suggestions on making real sure my memory is getting cleared (maybe Process.Id is getting twisted up in all the opening and closing?)
Suggestions on ordering a parallel process - I'm wondering if I can throw the 'big' guys in first, that will make the longer-running process more stable.
I have been looking at: http://reedcopsey.com/2010/01/26/parallelism-in-net-part-5-partitioning-of-work/ and he says "With prior knowledge about your work, it may be possible to partition data more meaningfully than the default Partitioner." But I'm having a hard time really knowing what/if partitioning makes sense.
Really appreciate any insights!
UPDATE
So as a general rule I test against Excel 2010, as we have both 2010 and 2013 under use here. I ran it against 2013 and it works fine - run time about 4 minutes, which is about what I would expect. Before I just abandon 2010 compatibility, any other ideas? The 2010 machine is a 64-bit machine with 64-bit Office, and the 2013 machine is a 64-bit machine with a 32-bit Office. Would that matter at all?
A few years ago i worked with excel files and automation. I then had problems of having zombie processes in task manager. Although our program ended and i thought i quit excel properly, the processes were not quitting.
The solution was not something i liked but it was effective. I can summarize the solution like this.
1) never use two dots consecutively like:
workBook.ActiveSheet.PageSetup
instead use variables.. when you are done relase and null them.
example: instead of doing this:
m_currentWorkBook.ActiveSheet.PageSetup.LeftFooter = str.ToString();
follow the practices in this function. (This function adds a barcode to excel footer.)
private bool SetBarcode(string text)
{
Excel._Worksheet sheet;
sheet = (Excel._Worksheet)m_currentWorkbook.ActiveSheet;
try
{
StringBuilder str = new StringBuilder();
str.Append(#"&""IDAutomationHC39M,Regular""&22(");
str.Append(text);
str.Append(")");
Excel.PageSetup setup;
setup = sheet.PageSetup;
try
{
setup.LeftFooter = str.ToString();
}
finally
{
RemoveReference(setup);
setup = null;
}
}
finally
{
RemoveReference(sheet);
sheet = null;
}
return true;
}
Here is the RemoveReference function (putting null in this function did not work)
private void RemoveReference(object o)
{
try
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(o);
}
catch
{ }
finally
{
o = null;
}
}
If you follow this pattern EVERYWHERE it guarantees no leaks, no zombie processes etc..
2) In order to create excel files you can use excel application, however to get data from excel, i suggesst using OleDB. You can approach excel like a database and get data from it with sql queries, datatables etc.
Sample Code: (instead of filling dataset, you can use datareader for memory performance)
private List<DataTable> getMovieTables()
{
List<DataTable> movieTables = new List<DataTable>();
var connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + excelFilePath + ";Extended Properties=\"Excel 12.0;IMEX=1;HDR=NO;TypeGuessRows=0;ImportMixedTypes=Text\""; ;
using (var conn = new OleDbConnection(connectionString))
{
conn.Open();
DataRowCollection sheets = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" }).Rows;
foreach (DataRow sheet in sheets)
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT * FROM [" + sheet["TABLE_NAME"].ToString() + "] ";
var adapter = new OleDbDataAdapter(cmd);
var ds = new DataSet();
try
{
adapter.Fill(ds);
movieTables.Add(ds.Tables[0]);
}
catch (Exception ex)
{
//Debug.WriteLine(ex.ToString());
continue;
}
}
}
}
return movieTables;
}
As an alternative solution to the one proposed by #Mustafa Düman I recommend you to use Version 4 beta of EPPlus. I used it without problems in several projects.
Pros:
Fast
No memory leaks (I can't tell the same for versions <4)
Does not require Office to be installed on the machine where you use it
Cons:
Can be used only for .xlsx files ( Excel 2007 / 2010 )
I tested it with the following code on 20 excel files around 12.5 MB each (over 50k records in each file) and I think it's enough to mention that it didn't crashed :)
Console.Write("Path: ");
var path = Console.ReadLine();
var dirInfo = new DirectoryInfo(path);
while (string.IsNullOrWhiteSpace(path) || !dirInfo.Exists)
{
Console.WriteLine("Invalid path");
Console.Write("Path: ");
path = Console.ReadLine();
dirInfo = new DirectoryInfo(path);
}
string[] files = null;
try
{
files = Directory.GetFiles(path, "*.xlsx", SearchOption.AllDirectories);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
return;
}
Console.WriteLine("{0} files found.", files.Length);
if (files.Length == 0)
{
Console.ReadLine();
return;
}
int succeded = 0;
int failed = 0;
Action<string> LoadToDataSet = (filePath) =>
{
try
{
FileInfo fileInfo = new FileInfo(filePath);
using (ExcelPackage excel = new ExcelPackage(fileInfo))
using (DataSet dataSet = new DataSet())
{
int workSheetCount = excel.Workbook.Worksheets.Count;
for (int i = 1; i <= workSheetCount; i++)
{
var worksheet = excel.Workbook.Worksheets[i];
var dimension = worksheet.Dimension;
if (dimension == null)
continue;
bool hasData = dimension.End.Row >= 1;
if (!hasData)
continue;
DataTable dataTable = new DataTable();
//add columns
foreach (var firstRowCell in worksheet.Cells[1, 1, 1, dimension.End.Column])
dataTable.Columns.Add(firstRowCell.Start.Address);
for (int j = 0; j < dimension.End.Row; j++)
dataTable.Rows.Add(worksheet.Cells[j + 1, 1, j + 1, dimension.End.Column].Select(erb => erb.Value).ToArray());
dataSet.Tables.Add(dataTable);
}
dataSet.Clear();
dataSet.Tables.Clear();
}
Interlocked.Increment(ref succeded);
}
catch (Exception)
{
Interlocked.Increment(ref failed);
}
};
Stopwatch sw = new Stopwatch();
sw.Start();
files.AsParallel().ForAll(LoadToDataSet);
sw.Stop();
Console.WriteLine("{0} succeded, {1} failed in {2} seconds", succeded, failed, sw.Elapsed.TotalSeconds);
Console.ReadLine();