Selection.InsertFile fails with "disk is full" error message - c#

With c# and Interop Word I am trying to merge all the files in a specified directory into a new document. My code loops through a list of file names and uses "InsertFile" to add each one to a Selection.
There are a lot of files, and the process is failing after a while. The error message reads:
The disk is full. Free some space on this drive, or save the document on another disk.
Try one or more of the following:
Close any unneeded documents, programs, and windows.
Save the document on another disk."
At the point where the InsertFile fails the selection is using about 7MB. My disk has 300GB of free space and the machine has 32GB of RAM.
The files I am saving do not have any graphics or Math in them.
What am I doing wrong? The exception is getting thrown on the InsertFile line inside the foreach loop (below).
The code follows in a second. Please note that I start from a winword instance, which is a member of a class called PrintObj. Also, please note that this code works fine if I use it with a small number of files, like 100-200).
Document mergeDoc = PrintObj.WinWord.Documents.Add(ref missing, ref missing,
ref missing, ref missing);
mergeDoc.PageSetup.LeftMargin = globalPrintObj.WinWord.InchesToPoints(0.5f);
mergeDoc.PageSetup.RightMargin =
PrintObj.WinWord.InchesToPoints(0.5f);
string[] filePaths = Directory.GetFiles(directoryPath, "*.docx");
string[] documentsToMerge = filePaths;
Array.Sort(documentsToMerge);
// Make a Word selection object.
Selection selection = PrintObj.WinWord.Selection;
//A counter that signals that we shoudn't insert a page break at the end of
document.
int breakStop = 0;
//Count the number of documents to insert;
int documentCount = documentsToMerge.Length;
// Loop thru each of the Word documents
foreach (string file in documentsToMerge)
{
breakStop++;
// Insert the files to our template
selection.InsertFile(file, ref missing, ref missing, ref missing,
ref missing);
if (breakStop != documentCount)
{
selection.InsertBreak(ref pageBreak);
}
}
Directory.CreateDirectory(directoryPath);
//Save the document
obj wordFileName = fileNameWithExtension;
mergeDoc.SaveAs2(ref wordFileName);

Could you check the path with the name of the files?
sometime ago i remember got that error of Disk full for the 255 characters problem, maybe one of those files has a too longe path
https://support.code42.com/CrashPlan/4/Troubleshooting/Windows_file_paths_longer_than_255_characters
try save the files in a different directory, like C:\YourFolder

Eventually I gave up on the MS assemblies and used the Xceed.Words.NET to solve this, which works. When using the Selection object the process always hung after about 840 insert docs (it was always the same exact number of insert docs), which was a problem because I needed to insert more than 1200 files. The final doc currently has just over 15,706,000 characters and over 11,700 pages, and is just over 4MB.
It seems to me that this is a flaw in the Interop Word Document Class because appending documents is a pretty common use case.
((Note added a couple of weeks later - had a chance to go back and try adding a counter every time an insert is done and then
if (Counter % 100 == 0)
{
mergeDoc.UndoClear();
}
This worked. Thank you to CM who suggested it.

Related

How to copy specific pages of Word document

I have a word document which contains multiple pages and i want to copy some pages into new word document using OpenXml SDK. I did some web search and got below code which reads entire document and copies into new one
string documentURL = filelocation;
byte[] docAsArray = File.ReadAllBytes(documentURL);
using (MemoryStream stream = new MemoryStream())
{
stream.Write(docAsArray, 0, docAsArray.Length); // THIS performs doc copy
using (DocumentFormat.OpenXml.Packaging.WordprocessingDocument doc = DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Open(stream, true))
{
// perform content control substitution here, making sure to call .Save()
// on any documents Part's changed.
}
File.WriteAllBytes(outputSplitDocpath, stream.ToArray());
}
Now, in the above code how can i read just specific pages and copy into new one? Please help with suggestions. Thanks
Unless a manual page break has been used to generate every page in the document, what you want to do is not possible.
Automatic page breaks are generated by Word, at run-time, when the document is open in the Word application. The actual placement of a page break is completely dynamic, based on the editing being done and is recalculated "all the time" during editing.
This information is not reliably saved in the document when the document is closed. One reason for this is because the document could lay out differently when opened on a different machine, or when a different printer (driver) is selected.
So it's not possible to work with individual pages using the Office Open XML file format unless there's some way each page can be recognized, such as a manual page break.
Use Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application();
instead of OpenXML
//dummy value to satisfy params
object oMissing = System.Reflection.Missing.Value;
//copy specific page/s
object what = WdGoToItem.wdGoToPage;
object which = WdGoToDirection.wdGoToFirst;
object count1 = 1;
Range startRange = word.Selection.GoTo(ref what, ref which, ref count1, ref oMissing);
object count2 = (int)count + 1;
Range endRange = word.Selection.GoTo(ref what, ref which, ref count2, ref oMissing);
endRange.SetRange(startRange.Start, endRange.End - 1);
endRange.Select();
word.Selection.Copy();
//save...

How to remove the 'Read only recommended' option in a Word document using c#?

I'm working in an application that interact with Word. We import documents in our database then allow the user to modify those documents using Microsoft Word via NetOffice.WordApi.
We are having some issues when the document is marked as read-only, everytime is opened, we get a 'Save As' word dialog that gives you the option of creating a temporal copy of the document to allow you to make changes in the document.
My question is as follow: How can remove the read-only word mark of the document and re-save the document without the mark? I can remove the option manually from word following the instructions in the link below, but I want to automatise that process by code.
The author would like you to open this as read-only
** PLEASE BEFORE ANSWER NOTICE THE FOLLOWING: It's not the read-only property in the file at windows level, it's not an issue with the windows permision attibute. The file attributes are NOT Read-only the property is inside the word document. So changing the windows file permision attributes by code doesn't work, actually the files are not read-only when you check the file properties in windows, it's a word attribute which means the author marked the document as read-only when he saved it and will stop you to modify the file using word (file can be changed using a different software, it just read-only for word). Please don't sent me links about how to change permissions in windows at that's not the case, check the link for more info. **
Many thanks for your time.
The option you want to set is the Document.ReadOnlyRecommended property of the Word document.
When you open a Word document, you can actually set the ReadOnly property with the third argument in the Document.Open method. That argument won't, however, override the read-only recommended setting on a saved document. So if your document is saved with read-only recommended option, it will be opened as read-only when calling Document.Open.
So I think that you have two option:
Option 1
Set the Document.ReadOnlyRecommended to false before the Document is saved for the first time, similar to this
objDoc.ReadOnlyRecommended = false;
Option 2
If the document already is set to read-only recommended, you need to save the document as a new file with the Document.ReadOnlyRecommended property set to false using the Document.SaveAs2 method.
Your code might look like this:
object missing = System.Reflection.Missing.Value;
object readOnly = false;
object fileName = #"C:\User\MyFile.docx";
object newFileName = #"C:\User\MyNewFile.docx";
var objApp = new Application();
var objDoc = objApp.Documents.Open(ref fileName, ref missing, ref readOnly);
if (objDoc.ReadOnlyRecommended)
{
objDoc.SaveAs2(ref newFileName, ref missing, ref missing, ref missing, ref missing, ref missing, ref readOnly);
}
objDoc.Close();
objApp.Quit();

Duplicate an Excel chart and move it to another sheet

I am using the C# Excel interop and I want to create a copy of a chart from one sheet but I want this copy on another sheet. I have tried the following:
Excel.ChartObject chartTemplate = (Excel.ChartObject)sheetSource.ChartObjects("chart 1");
object o = chartTemplate.Duplicate();
Excel.ChartObject chart = (Excel.ChartObject)sheetSource.ChartObjects("chart 2");
chart.Name = "Skew" + expiry.ToString("MMMyy");
range = sheetDestination.Range["T" + chartRowCoutner.ToString()];
chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, range);
But when I try this, the last line throws an error:
An unhandled exception of type 'System.Exception' occurred in projectname.exe
Additional information: Error reading Excel file C:\ ...the file path...\template.xlsx: Value does not fall within the
expected range.
I have also tried passing a sheet in instead of a range:
chart.Chart.Location(Excel.XlChartLocation.xlLocationAsObject, sheetDestination);
but this gives the same error. I can't understand the reason for the error or how to fix it / bypass it.
I am trying to avoid bringing the clipboard into this, but even if I try copying and pasting, I can still only paste it as an image, which is really not ideal:
Excel.ChartArea chartArea = chart.ChartArea;
chartArea.Copy();
range = sheetDestination.Range["T" + chartRowCoutner.ToString()]; // Note that chart is not on the sheet sheetDestination
range.PasteSpecial(Excel.XlPasteType.xlPasteAll);
The only other solution I can think of now is to do this in VBA and then execute the macro via the interop. But surely it can be done in a clean way just using the interop without the clipboard.
You've already got the solution but instead of giving you a fish for a day I'll give you a proper answer that will help you with any C# Excel coding task.
The C# Interop Model for Excel is almost identical to the VBA Excel Model.
This means it's trivial to convert VBA recorded macros to C#. Let's try this with an exercise like moving a chart to a different sheet.
In the Developer Tab in Excel click Record Macro > right click Chart > select Move Chart > choose Object in: Sheet2 > click OK > click Stop Macro Recording.
To see the recorded Macro press Alt + F11 to bring up the VB Editor:
See in the above screenshot how VBA shows you the second parameter for Location() is Name and it's actually a string argument...
Let's convert this VBA Macro to C#:
EDIT by #Ama
The advice below is outdated, there's actually no need to worry about releasing COM objects, this is done automatically at RELEASE mode (DEBUG mode does not). See Hans Passant's answer to "Clean up Excel Interop Objects with IDisposable".
The trick here is: never use 2 dots with com objects.
Notice how I could have written:
var sheetSource = workbookWrapper.ComObject.Sheets["Sheet1"];
but that has two dots, so instead I write this:
var workbookComObject = workbookWrapper.ComObject;
var sheetSource = workbookComObject.Sheets["Sheet1"];
Ref: How do I properly clean up Excel interop objects?
You will see the AutoReleaseComObject code in the above QA that projects like VSTOContrib use.
Here is the complete code:
using Microsoft.Office.Interop.Excel;
...
var missing = Type.Missing;
using (AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application> excelApplicationWrapper = new AutoReleaseComObject<Microsoft.Office.Interop.Excel.Application>(new Microsoft.Office.Interop.Excel.Application()))
{
var excelApplicationWrapperComObject = excelApplicationWrapper.ComObject;
excelApplicationWrapperComObject.Visible = true;
var excelApplicationWrapperComObjectWkBooks = excelApplicationWrapperComObject.Workbooks;
try
{
using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapperComObjectWkBooks.Open(#"C:\Temp\ExcelMoveChart.xlsx", false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
{
var workbookComObject = workbookWrapper.ComObject;
Worksheet sheetSource = workbookComObject.Sheets["Sheet1"];
ChartObject chartObj = (ChartObject)sheetSource.ChartObjects("Chart 3");
Chart chart = chartObj.Chart;
chart.Location(XlChartLocation.xlLocationAsObject, "Sheet2");
ReleaseObject(chart);
ReleaseObject(chartObj);
ReleaseObject(sheetSource);
workbookComObject.Close(false);
}
}
finally
{
excelApplicationWrapperComObjectWkBooks.Close();
ReleaseObject(excelApplicationWrapperComObjectWkBooks);
excelApplicationWrapper.ComObject.Application.Quit();
excelApplicationWrapper.ComObject.Quit();
ReleaseObject(excelApplicationWrapper.ComObject.Application);
ReleaseObject(excelApplicationWrapper.ComObject);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
private static void ReleaseObject(object obj)
{
try
{
while (System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) > 0);
obj = null;
}
catch (Exception ex)
{
obj = null;
Console.WriteLine("Unable to release the Object " + ex.ToString());
}
}
I know Releasing all the Objects, using GC.Collect and not using two dots when assigning seems over the top but at least when I quit the instance of Excel the process is freed, I don't have to programmatically kill the Excel process!
Ref: Microsoft KB: Office application does not quit after automation from .NET client
From the MSDN documentation here:
https://msdn.microsoft.com/en-us/library/microsoft.office.tools.excel.chart.location.aspx
it states that for the Name parameter of type object:
Name
Type: System.Object
The name of the sheet where the chart is embedded if Where is xlLocationAsObject or the name of the new sheet if Where is xlLocationAsNewSheet.
This is somewhat misleading from the example at the bottom of the same linked page. It would appear from the example given, that you should actually pass a string of the sheet name. The pertinent line from the example is copied below (the example is for copying to a new sheet):
chart1.Location(Excel.XlChartLocation.xlLocationAsNewSheet,
"Sales");
So, for moving to an existing sheet, I would do:
chart1.Location(Excel.XlChartLocation.xlLocationAsObject,
"ExistingSheetName");
Do NOT pass a range, workbook or worksheet object. Try a string of the sheet name.
Now, from the same MSDN document page linked above, if you want to reposition the chart within the page once you have moved it to another sheet, there are additional instructions, repeated here for convenience:
If you want to move a chart to another position on a sheet, use the P:Microsoft.Office.Interop.Excel.ChartArea.Top property and P:Microsoft.Office.Interop.Excel.ChartArea.Left property of the ChartArea. You can get the ChartArea object of the Chart by using the ChartArea property.
If you're moving a chart to an existing sheet, be careful not to overlap your chart over existing data. If so, you will have to code around that separately.
This isn't the answer to the question you asked, but might be fruitful
if you're making a copy and editing it for different variations THIS IS NOT A SOLUTION
if you're truly just copying a chart then I recommend using Excel's "Camera" function instead. It basically creates a window into another sheet - you can do this programmatically and it's well documented, but a little known feature of excel I thought I'd be remiss if I didn't point out.
-E
If you are looking to make edits & the question is still open let me know that in a comment - I've done this before I just need to look back in my workbook and see exactly how I did it.
'Camera option is nice because it doesn't 'recalculate' the data - so I imagine it operates faster; a concern in large workbooks.

Inserting word content into a VSTO document level customization

I have a VSTO document level customization that performs specific functionality when opened from within our application. Basically, we open normal documents from inside of our application and I copy the content from the normal docx file into the VSTO document file which is stored inside of our database.
var app = new Microsoft.Office.Interop.Word.Application();
var docs = app.Documents;
var vstoDoc = docs.Open(vstoDocPath);
var doc = docs.Open(currentDocPath);
doc.Range().Copy();
vstoDoc.Range().PasteAndFormat(WdRecoveryType.wdFormatOriginalFormatting);
Everything works great, however using the above code leaves out certain formatting related to the document. The code below fixes these issues, but there will most likely be more issues that I come across, as I come across them I could address them one by one ...
for (int i = 0; i < doc.Sections.Count; i++)
{
var footerFont = doc.Sections[i + 1].Footers.GetEnumerator();
var headerFont = doc.Sections[i + 1].Headers.GetEnumerator();
var footNoteFont = doc.Footnotes.GetEnumerator();
foreach (HeaderFooter foot in vstoDoc.Sections[i + 1].Footers)
{
footerFont.MoveNext();
foot.Range.Font.Name = ((HeaderFooter)footerFont.Current).Range.Font.Name;
}
foreach (HeaderFooter head in vstoDoc.Sections[i + 1].Headers)
{
headerFont.MoveNext();
head.Range.Font.Name = ((HeaderFooter)headerFont.Current).Range.Font.Name;
}
foreach (Footnote footNote in vstoDoc.Footnotes)
{
footNoteFont.MoveNext();
footNote.Range.Font.Name = ((Footnote)footNoteFont.Current).Range.Font.Name;
}
}
I need a fool proof safe way of copying the content of one docx file to another docx file while preserving formatting and eliminating the risk of corrupting the document. I've tried to use reflection to set the properties of the two documents to one another, the code does start to look a bit ugly and I always worry that certain properties that I'm setting may have undesirable side effects. I've also tried zipping and unzipping the docx files, editing the xml manually and then rezipping afterwards, this hasn't worked too well, I've ended up corrupting a few of the documents during this process.
If anyone has dealt with a similar issue in the past, please could you point me in the right direction.
Thank you for your time
This code copies and keeps source formatting.
bookmark.Range.Copy();
Document newDocument = WordInstance.Documents.Add();
newDocument.Activate();
newDocument.Application.CommandBars.ExecuteMso("PasteSourceFormatting");
There is one more elegant way to manage it based upon
Globals.ThisAddIn.Application.ActiveDocument.Range().ImportFragment(filePath);
or you can do the following
Globals.ThisAddIn.Application.Selection.Range.ImportFragment(filePath);
in order to obtain current range where filePath is a path to the document you are copping from.

InsertFile operation in word 2007 document throwing “Command failed” exception

I get the “Command Failed” exception when using the following code to insert content from one word 2007 document into another using bookmarks in c# :
string filePath = #“C:\temp\one.doc”;
object trueObj = true;
object falseObj = false;
wordApp.Selection.InsertFile( filePath, ref missing, ref falseObj, ref trueObj, ref falseObj );
"one.doc" is another word document containing table content.
Error code: -2146824090. This error generally comes when the target object is disposed or unavailable. Not sure why I am getting it here.
Also when I remove table content from the target document and I just add formatted text, the operation succeeds. When the same operation is performed through word GUI, the operation works fine. Have scoured the internet for pointers on this issue, but none were helpful in resolving this.
Thanks in advance,
Bharath K.
We have solved this problem by defining a macro that performs the above actions and invoking the macro using c# from my program. That worked!
I encountered this problem and I noticed that range where to insert the file contains some locked content controls. Before using InsertFile command make sure that range (Selection here) does not contain locked content controls.
Regards.

Categories