So I keep getting the "Application is busy" RPC_E_SERVERCALL_RETRYLATER error with the following code. It is worth noting that this exact code worked fine with Word 2003 and .doc files. After upgrading to 2007, it is no longer working. The file it getting the section count of is a ".docx" and I have made sure to use the proper version of the interop. The error occurs at a random location in the code usually.
public int GetSectionsCount(string fileName) {
wrdApp = new Application();
Object file = fileName;
Documents docs = wrdApp.Documents;
wrdDoc = docs.Open(ref file, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing,
ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing,
ref oMissing,
ref oMissing);
int count = wrdDoc.Sections.Count;
wrdDoc.Close(ref oFalse, ref oMissing, ref oMissing);
wrdApp.Quit(ref oFalse, ref oMissing, ref oMissing);
Marshal.ReleaseComObject(docs);
Marshal.ReleaseComObject(wrdDoc);
Marshal.ReleaseComObject(wrdApp);
wrdDoc = null;
wrdApp = null;
return count;
}
An example stacktrace:
at Microsoft.Office.Interop.Word.DocumentClass.get_Sections()
at MyApplication.WordMerge.split(String fileToSplit, String whereToSave, String quarterExtension, Form1 pb) in\\Projects\\MyApplication\\WordMerge.cs:line 176
at MyApplication.PMLettersManager.DoSplits() in \\Projects\\PyForms3\\PMLettersManager.cs:line 179
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
The code is run in its own thread and no other MS Word automation code is run concurrently. Again, it worked fine before upgrading.
EDIT: When I saved the file in question as a .DOC instead of .DOCX there were no errors and the code worked fine.
IIRC, the Word COM component is an STA one, so any call into it will need to be marshaled to the thread that created it.
If by any chance the thread that created it is not pumping messages the marshaling cannot occur and you get the error you are having.
I found the solution to this. When I saved the file as a .doc instead of .docx and tried to run the code, it ran without error. For some reason having a .docx file causes massive problems with COM. I could not find any other way that worked (including using COM error handling and message pump).
Related
I'm currently facing a problem regarding the MailMerge functionality of MS Word.
I had to rewrite an old VBA Application into C#. I'm practically done with that. New Application works fine.
Except for one PopUp that I cannot get rid of.
So I have been looking around on the web for the past 2 days because our clients don't want that pop up as it hasn't been there in the old application.
However I couldn't find a proper solution for this. Except a few people mentioning that probably the Connection string is incorrect. But I found no resources telling me how it should look in the C# code
This it how it looks in the old application:
Word.ActiveDocument.MailMerge.OpenDataSource Name:=strSourceDoc, ConfirmConversions:=False, _
ReadOnly:=False, LinkToSource:=True, AddToRecentFiles:=False, PasswordDocument:="", _
PasswordTemplate:="", WritePasswordDocument:="", WritePasswordTemplate:="", _
Revert:=False, Format:=wdOpenFormatAuto, Connection:= _
"Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source=" & strSourceDoc & ";Mode=Read;Extended Properties=""HDR=YES;IMEX=1;"";Jet OLEDB:System database="""";" _
, SQLStatement:="SELECT * FROM `Tabelle1$`", SQLStatement1:="", SubType:= _
wdMergeSubTypeAccess
I obviously tried already to take that connection key and use it in my code. But it does not prevent that pop up. I also tried playing around with the subtype. But it either doesn't change anything or throws a format exception.
This is whats working in C#:
mailApp.ActiveDocument.MailMerge.OpenDataSource(processedPath + file, true, false, true,
true, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
"Provider=Microsoft.ACE.OLEDB.12.0;User ID=Admin;Data Source=" +
processedPath + file + ";Mode=Read;",
"SELECT * FROM 'Tabelle1$'",
ref oMissing, ref oMissing, ref oMissing,
Word.WdMergeSubType.wdMergeSubTypeAccess);
How can I change the connection string to prevent that popup from showing?
So I found a solution, that is somehow working.
The actual problem (at least from what I tested) is the file extension not the connection string. I was using .xlsx files, as my source documents. But as soon as I tested with some xls Files the popup disapeared.
I just took a "google session" to find out the differences between xls and xlsx.
So I could change my code to work with xls Files only. Issue solved. But still an unpleasing solution for me tbh.
If you'd like to test around a little to maybe get it working with xlsx. Here is some code to test with (just bind it on a button click in winforms or something)
class PerformMailMerge
{
Word.Application mailApp;
Object oMissing = System.Reflection.Missing.Value;
Object oFalse = false;
string _path = #"Your Path to Excel File";
string savePath = #"Your Path to Word Document";
public void mailMerge2()
{
mailApp = new Word.Application();
mailApp.Visible = false;
mailApp.Documents.Add();
mailApp.ActiveDocument.MailMerge.MainDocumentType = Word.WdMailMergeMainDocType.wdMailingLabels;
//OpenDataSource:
mailApp.ActiveDocument.MailMerge.OpenDataSource(_path,
Word.WdOpenFormat.wdOpenFormatAllWordTemplates, true, true, true,
ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, "TABLE Tabelle1$", "SELECT * FROM `Tabelle1$",
ref oMissing, ref oMissing,
Word.WdMergeSubType.wdMergeSubTypeWord2000);
mailApp.ActiveDocument.SaveAs2(savePath);
mailApp.ActiveDocument.Close();
mailApp.Quit();
}
}
EDIT:
So in case anyone will stumble upon this again. I found a solution to the problem. The solution is NOT specifying a WdMergeSubType. This allows reading from xlsx files and still doesn't show the popup!
I have a windows application (in C#) which sends a DOC file to a pdf printer, creates pdf and read the bytes. The application uses word automation to print pdf using the following code
object Background = false; //to make sure the pdf file is created before continuing
wordApp.Visible = true;
wordApp.ActivePrinter = printerName;/*pdf printer*/
wordDoc = wordApp.Documents.Open(ref objFileName,
ref missing, ref objFalse, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing);
wordDoc.Activate();
wordDoc.PrintOut( Background, ref missing, ref Range, ref missing,
ref missing, ref missing, ref missing, ref Copies,
ref missing, ref PageType, ref PrintToFile, ref Collate,
ref missing, ref ManualDuplexPrint, ref PrintZoomColumn,
ref PrintZoomRow, ref missing, ref missing);
do
{System.Threading.Thread.Sleep(10);
} while (wordApp.BackgroundPrintingStatus > 0);
wordDoc.Close(ref objFalse, ref missing, ref missing);
After creating the pdf, i read the bytes using
bytes = System.IO.File.ReadAllBytes(Path.ChangeExtension(fileName, "pdf"));
Even though i set the background to false, it take a second or so for the file to be created in the directory, so readallbytes fails as it cannot find the file. I added the following code to wait for a time period so the pdf file appears
while (!File.Exists(Path.ChangeExtension(fileName, "pdf")))
{
System.Threading.Thread.Sleep(1000);
}
bytes = System.IO.File.ReadAllBytes(Path.ChangeExtension(fileName, "pdf"));
But sometimes i get exception that filename.pdf does not exist or i get process cannot access the file as it is being used by another process. I do not understand why file is not accessible as i am not doing anything that would lock the file and another thing i don't get it is i could read the bytes sometimes but thats does not happen always. I get not found error, does that mean the file is not created with in the sleep timeframe?
Instead of trying to do this with Thread.Sleep, I suggest you use a FileSystemWatcher object to monitor the folder for newly-created or newly-renamed files. This will allow you to set up an event to trigger the code for subsequent processing when the file is ready.
I'm using the Microsoft.Office.Interop.Word to manipulate the documents, things like creating a new document based on a template.
The only problem I'm having is to set the active document in Word to readonly. Keeping in mind that I open a new document based on a template, fill some fields with the necessary information, then a need to show this document to the user as readonly. This document still in memory (Doesn't have a path).
The library have a readonly attribute, but is readonly (Oh the irony...). Anybody have any suggestions that might help me?
Read-only is one of the defining features of the Document and thus it has to be set when the Document is created (added to the Application). Sample code:
bool readOnly = true;
Object templatePath = #"Path";
Object oMissing = System.Reflection.Missing.Value;
Word.Application wordApp = new Word.Application();
Word.Document wordDoc = wordApp.Documents.Open(templatePath, oMissing, readOnly, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing, oMissing);
I need to open docx that's stored in sharepoint then convert it to pdf in memory and then write to http response so pdf would be downloaded at client machine.
The issue is I don't know how to convert pdf to byte array in memory. There is a condition: I can use only free libs and third-side API. Microsoft.Interop offers to save docx as pdf to disk, for example:
Document doc = word.Documents.Open(ref filename, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing);
doc.Activate();
object outputFileName = wordFile.FullName.Replace(".doc", ".pdf");
object fileFormat = WdSaveFormat.wdFormatPDF;
// Save document into PDF Format
doc.SaveAs(ref outputFileName,
ref fileFormat, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing);
but I need to get bytes and write them to response directly without saving data on disk.
EDIT:
I'd like to apply more comprehensive one-stop solution without reference to SharePoint. Let's say in some ASP.NET site.
You can utilise the Word Automation Services Service in SharePoint 2010. There is a nice example at http://pholpar.wordpress.com/2011/10/27/using-sharepoint-2010-word-automation-services-to-convert-document-synchronously/ of how to do this in a synchronous manner.
I have a gridview, I need to export the gridview to MS word and this works fine, but I need to put custom header and footer on each page of Ms word. Is it possible to run a macro from asp.net on the exported gridview so that I can put the custom header and footer on the exported gridview and also adjust some margins on the printed out page.
any help will be appreciated.
Thanks.
This tutorial will show you how to call a macro using .NET. This is a summary of the steps relevant for your scenario (You may have some of this setup already if you have managed to export your GridView already):
Add a reference to the Microsoft Word Library:
On the Project menu, click Add Reference.
On the COM tab, locate Microsoft Word 10.0 Object Library or Microsoft Word 11.0 Object Library
Click Select
Then add these references to your code behind:
using System.Reflection;
using Word = Microsoft.Office.Interop.Word;
using Microsoft.Office.Core;
Add this helper method (which handles running the macro):
private void RunMacro(object oApp, object[] oRunArgs)
{
oApp.GetType().InvokeMember("Run",
System.Reflection.BindingFlags.Default |
System.Reflection.BindingFlags.InvokeMethod,
null, oApp, oRunArgs);
}
Then to call your macro use the following (You will need to modify this to suit your particular needs):
private void CallCustomHeaderFooterMacro(string filename, string pageTitle, string author)
{
Word.ApplicationClass oWord = new Word.ApplicationClass();
oWord.Visible = false;
Word.Documents oDocs = oWord.Documents;
object oFile = filename;
// Used for optional arguments
object oMissing = System.Reflection.Missing.Value;
// CHOOSE THE APPROPRIATE CALL, DEPENDING ON THE LIBRARY YOU REFERENCE
// Microsoft Word 10.0 Object Library
Word._Document oDoc = oDocs.Open(ref oFile, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing);
// Microsoft Word 11.0 Object Library
// Word._Document oDoc = oDocs.Open(ref oFile, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing);
// Run the 'CustomHeaderFooter' Macro.
/* Change "CustomHeaderFooter" to your macro name.
* You can send parameters to your macro such as:
* a Page title, author, date or other data you need to create the customised
* header and footer. You add and remove these as required. I have used pageTitle
* and author as an example
*/
RunMacro(oWord, new Object[]{"CustomHeaderFooter", pageTitle, author});
// Save (as required)
// Quit Word and clean up.
oDoc.Close(ref oMissing, ref oMissing, ref oMissing);
System.Runtime.InteropServices.Marshal.ReleaseComObject (oDoc);
oDoc = null;
System.Runtime.InteropServices.Marshal.ReleaseComObject (oDocs);
oDocs = null;
oWord.Quit(ref oMissing, ref oMissing, ref oMissing);
System.Runtime.InteropServices.Marshal.ReleaseComObject (oWord);
oWord = null;
}
This will work if you have setup a macro, such as this, in your document.
Public Sub CustomHeaderFooter(sPageTitle As String, sAuthor As String)
' Your code to create the header and footer as required.
' The easiest way to get the macro code is to open the base document and
' and record the actions that you need such as adjusting the print margins
' and adding the custom headers and footers, then modify the resultant code
' to accept your parameters.
End Sub
In you ASP.NET you would call (Adjust to your file path and appropriate parameters):
CallCustomHeaderFooterMacro(Server.MapPath("~/mydocument.docx"), "Sample Title", "John Smith");
I hope this is clear. Obviously I can't provide the macro code to produce the actual header and footer, but as above, the easiest path is to record the actions and manually adjust the generated code.
Edit: Just as a side note. Microsoft doesn't recommend the use of Office automation from server-side applications, i.e. calling from ASP.NET. This article explains here and offers alternative mechanisms for manipulating Office documents, which may or may not be useful to you. But I thought it may help, depending on the scalability your project requires.
I don't think you can do that from asp.net. But may be you can create a VBA that runs when Word is open and the filename equals the same file name created by the asp.net export?