0
votes

I'm writing a C# app that goes through a list of Word documents and changes their attached template using Interop. The idea is that if a bunch of Word documents are pointing to templates on a server that doesn't exist, we can use string replace to change the templates' paths to the correct ones.

Note that each document may have one of several different templates, and therefore I can't just change all of them to a specific path - I must use string replace.

My problem is that my Interop.Word.Document object doesn't return the correct attached template. When I open a Word document in MS Word and go to the templates window, I see that the attached template is a network file template that doesn't exist anymore. That's the path I want to change. But when I use get_AttachedTemplate() in C#, I get the path to another template...

Can anyone help me?

Note: I'm using Microsoft Word 16.0 Object Library.

using System;
using System.IO;
using System.Reflection;
using Microsoft.Office.Interop.Word;
using App = Microsoft.Office.Interop.Word.Application;
using Doc = Microsoft.Office.Interop.Word.Document;

namespace OfficeTemplateCleaner
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.Write(@"Enter a string to search (e.g. \\old\path\): ");
            var stringToFind = Console.ReadLine();
            Console.Write($"Enter a string to replace \"{stringToFind}\": ");
            var replaceWith = Console.ReadLine();

            var files = new[]
            {
                @"\\path\to\one\test\document.doc"
            };

            App oWord = new App() { Visible = false };
            foreach (var file in files)
            {
                if (!File.Exists(file))
                {
                    continue;
                }

                // cache file modification times so that we can revert them to what they were originally
                var fileInfo = new FileInfo(file);
                var modifyTime = fileInfo.LastWriteTime;

                //OBJECT OF MISSING "NULL VALUE"
                object oMissing = Missing.Value;
                //OBJECTS OF FALSE AND TRUE
                object oTrue = true;
                object oFalse = false;
                //CREATING OBJECTS OF WORD AND DOCUMENT
                Doc oWordDoc = new Doc();
                //MAKING THE APPLICATION VISIBLE
                oWord.Visible = true;
                //ADDING A NEW DOCUMENT TO THE APPLICATION
                oWordDoc = oWord.Documents.Open(file);

                var templ = (Template) oWordDoc.get_AttachedTemplate();
                var template = templ.FullName;
                if (template.IndexOf(stringToFind) == -1)
                {
                    continue;
                }

                var newTemplate = template.Replace(stringToFind, replaceWith);
                oWordDoc.set_AttachedTemplate(newTemplate);

                oWordDoc.SaveAs(file);
                oWordDoc.Close();

                fileInfo = new FileInfo(file);
                fileInfo.LastWriteTime = modifyTime;
            }
            oWord.Quit();

            Console.WriteLine("Done.");
            Console.ReadLine();
        }
    }
}
1

1 Answers

0
votes

This is a common issue with the Word interop: if the template location is not available then it's not possible to access this value. Word returns the template it can actually "reach" which by default will usually be Normal.dotm if the attached template cannot be found.

More reliable would be to work with the closed file via the Open XML SDK (it will be much faster, as well). Here's some code that will access the current information and change it. You will, of course, need to add the logic for which file is to be set as the new attached template.

string filePath = @"C:\Test\DocCopyTest.docm"; 

using (WordprocessingDocument pkgDoc = WordprocessingDocument.Open(filePath, true))
{
    //Gets only the file name, not path; included for info purposes, only
    DocumentFormat.OpenXml.ExtendedProperties.Template t = pkgDoc.ExtendedFilePropertiesPart.Properties.Template;
    string tName = t.Text;
    this.txtMessages.Text = tName;

    //The attached template information is stored in the DocumentSettings part
    //as a link to an external resource. So the information is not directly in the XML
   //it's part of the "rels", meaning it has to be accessed indirectly
    MainDocumentPart partMainDoc = pkgDoc.MainDocumentPart;
    ExternalRelationship r = null;
    string exRel_Id = "";
    string exRelType = "";
   //the file location of the new template, as a URI
    Uri rUri = new Uri("file:////C:/Test/DocCopy_Test2.dotm");
    Array exRels = partMainDoc.DocumentSettingsPart.ExternalRelationships.ToArray();
    foreach (ExternalRelationship exRel in exRels)
        if (exRel.RelationshipType.Contains("attachedTemplate"))
        {
            exRel_Id = exRel.Id;
            exRelType = exRel.RelationshipType;
            System.Diagnostics.Debug.Print(exRel_Id + " " + exRelType);
            partMainDoc.DocumentSettingsPart.DeleteExternalRelationship(exRel);
            r = partMainDoc.DocumentSettingsPart.AddExternalRelationship(exRelType, rUri, exRel_Id);
            System.Diagnostics.Debug.Print(r.Uri.ToString());
            break;
        }
}