6

I'm doing several things with a Word document in the background and sometimes, if the app fails, an instance of MS Word is left running in the background. Upon restart, it tries to open the same file and there's a stream of issues related to that.

I'd like to understand (couldn't quite find an applicable answer here) how to check if the file I'm trying to open is already open.

My code:

Microsoft.Office.Interop.Word.Application wordApp = new Microsoft.Office.Interop.Word.Application();
Document wordDoc = new Document();
wordDoc = wordApp.Documents.Open(ref oTemplatePath, ref oMissing, ref oMissing, ref oMissing);
wordApp.Visible = false;

How can I check for the file being already open before executing wordApp.Documents.Open?

Thanks!

Paulo Hgo
  • 834
  • 1
  • 11
  • 26
  • I faced a similar issue with the Excel Interop API, where if you do not release COM objects properly, the app will just keep running without a window even after you call the `Quit()` method. Instead of checking if the doc is already open, you should be making sure that Word is disposed correctly in the first place. [Try this thread](https://stackoverflow.com/questions/6777422/disposing-of-microsoft-office-interop-word-application), there is a good chance that this is what is happening. – Nik Oct 30 '17 at 04:03
  • Can you not look for if the file is opened or not using System.IO. Refer: https://stackoverflow.com/questions/876473/is-there-a-way-to-check-if-a-file-is-in-use#answer-937558 – Ankit Vijay Oct 30 '17 at 04:11
  • Thank you Nik. I am already doing that when the app ends OK. There are a few circumstances where it may fail and it leaves that instance running. I'd like to kill that but if the app is run again, a whole new instance is created. I guess that the real question should be "How do I kill an existing version of MS Word that's running in the background"? – Paulo Hgo Oct 30 '17 at 04:32
  • haha, I did that for a bit. It is a memory leak for all intents and purposes, and you just need to clean it up. A quick and dirty way would be to use the `Process.GetProcesses()`, then find all `Word` processes by name and then `process.Kill()`. You don't want to kill Word if it has a window (i.e. a user is actually using it), so check `if(process.MainWindowHandle == (IntPtr)0)`, as it would be true if `Word` is running silently. Let me know if more clarification is needed. – Nik Oct 30 '17 at 04:56
  • Also, @PauloHgo, see [How to reply in comments](https://meta.stackexchange.com/questions/43019/how-do-comment-replies-work). I may have missed yours. – Nik Oct 30 '17 at 04:58

4 Answers4

5

You can try to open the file, if the file is already open IOException will be thrown and indicate that the file is open:

public bool fileIsOpen(string path)
{
    System.IO.FileStream a = null;

    try
    {
        a = System.IO.File.Open(path,
        System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.None);
        return false;
    }
    catch (System.IO.IOException ex)
    {
        return true;
    }

    finally
    {
        if (a != null)
        {
            a.Close();
            a.Dispose();
        }
    }
}
Jonathan Applebaum
  • 5,738
  • 4
  • 33
  • 52
3

Had the same Problem. My solution was to always open the document as ReadOnly and make a copy of it. I know the question was for a way to check if the document is already opened, but it is not necessary to check this if the document is opened readonly:

public static MsWord.Document CopyTemplate(string Template, MsWord.Application wordApp)
{
      var TemplateDoc = wordApp.Documents.Open(Template, ReadOnly: true);

      TemplateDoc.Select();
      wordApp.Selection.Copy();
      TemplateDoc.Close(SaveChanges: false);

      var NewDoc = wordApp.Documents.Add();
      NewDoc.Select();
      wordApp.Selection.PasteAndFormat(MsWord.WdRecoveryType.wdFormatOriginalFormatting);

      return NewDoc;
}
SErnst
  • 121
  • 1
  • 4
2

You can enumerate all Word windows and try to find one with that document. Also if you try to open this file, while it's opened in Word, you will get IOException. But it's not perfect solution :( Here is solution:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace WordExampleApp
{
    class Program
    {
        const int ERROR_SHARING_VIOLATION = -2147024864;
        static readonly string WORD_CLASS_NAME = "OpusApp";
        static readonly string WORD_DOCUMENT_CLASS_NAME = "_WwB";

        delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);

        static void Main(string[] args)
        {
            // Path to exist word document
            string filePath = @"C:\temp\asdasd.docx";

            string documentName = Path.GetFileNameWithoutExtension(filePath);
            var isDocOpened = IsDocumentOpened(documentName);
            Console.WriteLine("Is document opened: {0}", isDocOpened);

            bool canRead = CanReadFile(filePath);
            Console.WriteLine("Can read file: {0}", canRead);
        }
        private static bool CanReadFile(string path)
        {
            try {
                using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None)) { }
                return true;
            }
            catch (IOException ex) {
                if (ex.HResult == ERROR_SHARING_VIOLATION)
                    return false;
                else throw;
            }
        }

        private static bool IsDocumentOpened(string documentName)
        {
            IntPtr hwnd = FindWindow(WORD_CLASS_NAME, documentName + " - Word");
            List<IntPtr> childs = GetChildWindows(hwnd);
            var classText = new StringBuilder("", 1024);
            var windowText = new StringBuilder("", 1024);

            foreach (var childHwnd in childs)
            {
                if (0 == GetClassName(childHwnd, classText, 1024)) {
                    // something wrong
                }
                if (0 == GetWindowText(childHwnd, windowText, 1024)) {
                    // something wrong
                }
                var className = classText.ToString();
                var windowName = windowText.ToString();
                if (className == WORD_DOCUMENT_CLASS_NAME && windowName == documentName)
                    return true;

                classText.Clear();
                windowText.Clear();
            }
            return false;
        }
        public static List<IntPtr> GetChildWindows(IntPtr parent)
        {
            List<IntPtr> result = new List<IntPtr>();
            GCHandle gcHandle = GCHandle.Alloc(result);
            try {
                EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
                EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(gcHandle));
            }
            finally {
                if (gcHandle.IsAllocated)
                    gcHandle.Free();
            }
            return result;
        }
        private static bool EnumWindow(IntPtr handle, IntPtr pointer)
        {
            GCHandle gch = GCHandle.FromIntPtr(pointer);
            List<IntPtr> list = gch.Target as List<IntPtr>;
            if (list == null)
                throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
            list.Add(handle);
            return true;
        }

        [DllImport("user32", ExactSpelling = false, CharSet = CharSet.Auto)]
        internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
    }
}
Artavazd Balayan
  • 2,353
  • 1
  • 16
  • 25
0

Well I think a simple approach would be checking the title of current running processes:

bool isWordFileAlreadyOpened = false;
var alreadyOpenedProcessForWordFile = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle.Contains(alreadyOpenedProcessForWordFile));
isWordFileAlreadyOpened = alreadyOpenedProcess != null;
Ali Ezzat Odeh
  • 2,093
  • 1
  • 17
  • 17