MailMerge: From Excel to Word C# - c#

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!

Related

Open Microsoft word document from web project to any local machine

Below is my code on click to button. If I will run project using localhost it will open word document very well but when I am going to host this project on IIS and try to open it from another machine by IP it will transfer my page to error message.
Microsoft.Office.Interop.Word.ApplicationClass wordApp = new Microsoft.Office.Interop.Word.ApplicationClass();
object file = "D:\\poForM.docx";
object objFalse = false;
object objTrue = true;
object missing = System.Reflection.Missing.Value;
object emptyData = string.Empty;
object readOnly = false;
object visible = true;
wordApp.Visible = true;
Microsoft.Office.Interop.Word.Document aDoc = wordApp.Documents.Open(ref file, ref missing, ref readOnly, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, visible, ref missing, ref missing, ref missing, ref missing);
aDoc.Activate();
Your code is relying on there being an instance of Microsoft Word on the server (Microsoft.Office.Interop.Word.ApplicationClass)... and having the object file at a specified location on the server.
If I understand correctly what you are trying to do you want to host the Word document on your IIS server but download it onto the local machine for editing..
You can do this by providing a link in your Web page to where the Word doc is, for example:
Open Word Document
Then when you click the link the browser will download the doc and open it in Word (assuming it's installed locally)
Of course if I've completely misunderstood what you're trying to do feel free to comment....
I believe you want to achieve SharePoint alike behaviour where the user can open the file and then save it back to server. Here is a similar thread Possible for Word to edit documents directly off an web server without Sharepoint? . The only bad thing about this solution is that AFAIK it is working only in IE. You can also try new ActiveXObject("Word.Application");instead of the new ActiveXObject("SharePoint.OpenDocuments");

DCOM Config settings for Microsoft Interop Word Automation

I am using Microsoft Office Interop Word to generate documents using C#. In order for the document generation to work, there should be an entry for the "Microsoft Office Word 97 - 2003 Document" under the Dcom Config Settings as shown below:
The Local Path under the General Tab has a correct path when Microsoft Office is first installed. If I then join the computer to a Domain, and then restart the system with a Domain user, the Local Path becomes blank and the application doesn't generate the documents and gives error.
Even if I join the computer to Domain first and then login with Domain user and then install the Microsoft Office, the Local Path appears correct first and then after a restart, it becomes blank again. While, at the same time, if I login with the Local User, the Path is still there.
What is causing the value of Local Path to go blank?
This all setup is on virtual machines and the word automation works on a domain account as I have seen it working on a physical machine joined to domain.
UPDATE: What my application is doing:
There are 4-5 components in my application.
The first is a VSTO Word AddIn, which integrates with Microsoft Word, where we create new documents that contain some Expressions that are also saved in the database. There are also conditions on the Expressions and they can be nested also. Expressions contain schema elements from XSD files which are saved in database. Once this type of document is created, its WordML is saved in the database. This all is done in VSTO AddIn.
The second is a Web Service which receives an input xml from another component that confirms to the XSD above from which the schema elements were embedded into the expressions in the document created through VSTO addIn. This web service checks for the validations and several other tasks. It then gets the WordML of the corresponding word document from the database and passes it to the Word Interop which using its APIs, iterates through it recursively to replace the schema elements with their actual values from the input xml. This then saves the WordML to a file as word document.
This also attaches a template to the document before saving it. It uses the SaveAs functionality of Word Interop to also save the file as PDF.
UPDATE:
I have again gone through my complete application and came to know that we are doing all things by parsing the Office Open XML (e.g. for feeding the input to the word document), but the only things that we are doing using Word Automation are following:
Using Word Interop to save the generated WordML as one of the Word Format Files.
Exporting the generated WordML to the PDF file.
Merging several WordMLs into a single word document file.
Fetching the XML for it.
All these four codes are shown below with only relevant parts of code:
Microsoft.Office.Interop.Word.Document wordDocument = null;
object templateName = "templateFile.dotm";
wordDocument = this.WordApplication.Documents.Add(ref missing, ref missing, ref missing, ref missing);
wordDocument.Range(ref missing, ref missing).Text = "";
wordDocument.set_AttachedTemplate(ref templateName);
wordDocument = this.WordApplication.Documents.Open(
ref objSourceFilePath, ref oFalse, ref oTrue,
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);
wordDocument.ExportAsFixedFormat(
strTargetPath,
targetFormat,
paramOpenAfterExport,
paramExportOptimizeFor,
paramExportRange,
paramStartPage,
paramEndPage,
paramExportItem,
paramIncludeDocProps,
paramKeepIRM,
paramCreateBookmarks,
paramDocStructureTags,
paramBitmapMissingFonts,
paramUseISO19005_1,
ref oMissing);
object SaveToFormat = SaveToFormat = Microsoft.Office.Interop.Word.WdSaveFormat.wdFormatDocument97;
wordDocument.SaveAs(ref objTargetFilePath, ref SaveToFormat, 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);
For Merging several files:
Microsoft.Office.Interop.Word.Document doc = null;
Microsoft.Office.Interop.Word.Section section = null;
object sectionBreakNextPage = (object)WdBreakType.wdSectionBreakNextPage;
WordApp.Visible = false;
doc = this.WordApplication.Documents.Add(ref paramMissing, ref paramMissing, ref paramMissing, ref paramMissing);
if (doc != null)
{
doc.Activate();
int fileCount = sourceFiles.Length;
String fileName = string.Empty;
for (int fileIndex = 0; fileIndex < fileCount; fileIndex++)
{
fileName = sourceFiles[fileIndex];
if (System.IO.File.Exists(fileName))
{
section = doc.Sections.Last;
//delink the current section's header & footer from previous section's header & footer
section.Headers[WdHeaderFooterIndex.wdHeaderFooterFirstPage].LinkToPrevious = false;
section.Footers[WdHeaderFooterIndex.wdHeaderFooterFirstPage].LinkToPrevious = false;
section.Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].LinkToPrevious = false;
section.Footers[WdHeaderFooterIndex.wdHeaderFooterPrimary].LinkToPrevious = false;
section.Headers[WdHeaderFooterIndex.wdHeaderFooterEvenPages].LinkToPrevious = false;
section.Footers[WdHeaderFooterIndex.wdHeaderFooterEvenPages].LinkToPrevious = false;
section.Range.InsertFile(fileName, ref paramMissing, ref paramMissing, ref paramMissing, ref paramMissing);
//if it is last iteration, do'nt insert break
if (fileIndex < fileCount - 1)
{
object rangeStart = (object)(section.Range.End - 1);
doc.Range(ref rangeStart, ref paramMissing).InsertBreak(ref sectionBreakNextPage);
}
}
}
doc.SaveAs(ref targetFile, ref wordFormat, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing, ref paramMissing, ref paramMissing,
ref paramMissing);
return true;
}
Right now, I am receiving the following error:
The message filter indicated that the application is busy. (Exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER))
Can this all be done without using the Word Automation?
Instead of trying to fix and deal with that error, I think you should read this and then try another approach to your problem:
(...) Microsoft does not currently recommend, and does not support, Automation of Microsoft Office applications from any unattended, non-interactive client application or component (including ASP, DCOM, and NT Services), because Office may exhibit unstable behavior and/or deadlock when run in this environment. (...)
Some of the alternatives recommended in that KB are:
(...) Microsoft strongly recommends a number of alternatives that do not require Office to be installed server-side, and that can perform most common tasks more efficiently and more quickly than Automation. Before you involve Office as a server-side component in your project, consider alternatives.
Most server-side Automation tasks involve document creation or editing. Office 2007 supports new Open XML file formats that let developers create, edit, read, and transform file content on the server side. These file formats use the System.IO.Package.IO namespace in the Microsoft .NET 3.x Framework to edit Office files without using the Office client applications themselves. This is the recommended and supported method for handling changes to Office files from a service. (...)
And
(...) Microsoft provides an SDK for manipulating Open XML file formats from the .NET 3.x Framework. For more information about the SDK and about how to use the SDK to create or edit Open XML files, visit the following Microsoft Developer Network (MSDN) Web sites:
Open XML SDK Documentation
How to: Manipulate Office Open XML Formats Documents
Manipulating Word 2007 Files with the Open XML Object Model (Part 1 of 3)
Manipulating Word 2007 Files with the Open XML Object Model (Part 2 of 3)
Manipulating Word 2007 Files with the Open XML Object Model (Part 3 of 3) (...)
Note, that even if you get your problem fixed, your solution will hardly be stable... In essence what, it seems to be happening is that you messed your registry and it seems that your Word reinstallation is not fixing your registry, and that is problematic.
Based on this, I recommend you to read the above documentation and to try put together a more stable solution using the above alternatives, as Automation of Microsoft Office applications from any unattended, non-interactive client application or component, which is your case, may exhibit unstable behaviors.
UPDATE 1
You have an Hello World example here.
Creating a document with Open XML can be as easy as doing this:
public void HelloWorld(string docName)
{
// Create a Wordprocessing document.
using (WordprocessingDocument package = WordprocessingDocument.Create(docName, WordprocessingDocumentType.Document))
{
// Add a new main document part.
package.AddMainDocumentPart();
// Create the Document DOM.
package.MainDocumentPart.Document =
new Document(
new Body(
new Paragraph(
new Run(
new Text("Hello World!")))));
// Save changes to the main document part.
package.MainDocumentPart.Document.Save();
}
}
Note
I could spent here hours, trying to solve your registry, but as you can see in this article in my blog, those problem are huge headaches, and in your case, even if you find a way to solve it, it will not be a maintainable or either scalable solution, in my opinion of course.
UPDATE 2
According to this, those configurations such as local path are extracted from the registry and are not modifiable:
(...) The General tab provides general information about the application. This tab displays the Application name, type (local server or remote server), and location (local path or remote computer). These settings are not modifiable through the DCOM Config interface.
The General Tab retrieves all of its information from subkeys of the following registry key:
HKEY_CLASSES_ROOT\CLSID{...CLSID...}
where {...CLSID...} is the unique CLSID for the Object Server currently being viewed. (...)
So! Run > regedit > Go to HKEY_CLASSES_ROOT\CLSID, then go to Edit menu and click Find, filter by key and put your ApplicationID there. You should find it this way.
Now after finding the registry entry for your DCOM, expand it, you should see a LocalServer32, the property (Default) holds your Local Path value, try change it the same path as in your new Oracle Virtual Box.
If this works, test if the value hold after restarting and logging in with you Domain User account if it does, great, if not, run a batch to run a .reg file to perform this modification, on every login.
Warning: Nevertheless, this is bad stuff, I strongly, strongly encourage you to go the other way around, this is not the way to do it.
UPDATE 3
Regarding the "MS-WORD AUTOMATION ERROR : "The message filter indicated that the application is busy", you have a very good reply to that problem here. I'll cite a bit of the above link, for further understanding of why that error happens:
(...) That issue is the fact that the Word objects you are calling into do not support multiple threading. Since they are exposed to arbitrary clients via COM, the possibility exists that multiple threads could attempt to simultaneously execute code within the object. To prevent this from happening, will serialize all incoming calls by queuing them up and only allowing one call at a time to execute. This is done by packaging up the details of each call and posting a message to Word. When Word processes, the message, the call will execute on Word's own main thread. The problem with this approach is that if Word is busy doing something else when the call comes in, the caller will have to wait. (...)
About the merging, this tool claims to be capable of merging OpenXML documents, I never used it, but I would give it a try (if I were you).
(...) PowerTools for Open XML contains source code and guidance for accomplishing various common tasks using the Open XML SDK, such as: - High-fidelity conversion of DOCX to HTML/CSS using HtmlConverter.cs; - Merging and splitting DOCX documents using DocumentBuilder.cs; - Merging and splitting PPTX presentations using PresentationBuilder.cs; - Accepting tracked revisions in DOCX documents using RevisionAccepter.cs; - Searching and replacing text in DOCX documents using TextReplacer.cs (...)
Finally to generate PDFs, from your word documents, you can use this tool here.
So as you can see, once more, you can keep dealing with Word Automation (Dark Side), or you can join the Light Side of the Force :).

Application.Documents.Add return null

I try to use Microsoft.Office.Interop.Word to export a word by a word template.
Some related code below:
//strTemppath is a path for my word template(.doc).
object oTemplate = strTemppath;
var WordDoc = WordApp.Documents.Add(ref oTemplate, ref missing, ref missing, ref missing);
This works well locally. However, after I move my application to the server the "WordDoc" object always return null. How could this happen? Is there anything to do with the configuration of the server? such as "permission" or "Com components" or anything else?
Thanks in advance

running a macro from asp.net while exporting a gridview

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?

MS Word Interop C# RPC_E_SERVERCALL_RETRYLATER Error

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).

Categories