none
Starting Word from .NET without COMAddins RRS feed

  • Question

  • Hi

    In one of our projects the application prints out some selected Word documents.
    We have created a DLL attached to the application in order to do this.

    This solution has worked for many years, but more and more AddIns are added to the Office applications.

    This is beginning to be a problem for som users, as the printing sometimes failes, and we have discovered that
    if we disable the Active COM Addins printing problems stops.

    So, I thought it would be an easy task to turn of the COM Addins in code, but I was wrong.

    Even if the user can disable all addins in the options dialog, we are not allowed in code to do so for the addins which is loaded in machine level, even if the application is running as the same user.

    I know Word can be started with /a parameter which will do the trick, but need an application handle in the code in order to hide and print the documents.

    Does anybody have any suggestions on how to solve this without using macros?


    Best Regards Peter Karlström

    Thursday, January 11, 2018 8:16 AM

Answers

  • Hi Cindy

    Now I got it. This is how it's solved in VB.NET.

    Here is the calling sub:

    Private Sub TestWord_Click(sender As Object, e As EventArgs) Handles btnTest.Click
    
        Dim startInfo As New ProcessStartInfo("Winword.exe", "/a")
        startInfo.WindowStyle = ProcessWindowStyle.Hidden
    
        Dim wrd As Word.Application = Nothing
        Dim wProc As System.Diagnostics.Process = System.Diagnostics.Process.Start(startInfo)
        wdID = wProc.Id
        Dim wdTitle As String = wProc.MainWindowTitle
    
        Dim t As Timer = New Timer
        t.Start()
    
        While ((wdTitle.Length <= 0) AndAlso (t.Interval < 10))
            wdTitle = wProc.MainWindowTitle
        End While
    
        t.Stop()
        t = Nothing
    
        Dim cls As New WordLateBinding.Functions
        Threading.Thread.Sleep(500)
        wrd = cls.Main()
        If Not wrd Is Nothing Then
            'Do word stuff
            Dim aDoc As Word.Document = wrd.ActiveDocument
            Dim aSel As Word.Selection = aDoc.ActiveWindow.Selection
            aSel.TypeText("Hello World")
            wrd.Visible = True
        Else
            MsgBox("Word application couold not be found", MsgBoxStyle.Critical + MsgBoxStyle.OkOnly, "Testapplication Word")
        End If
    
    End Sub

    And here is the class used for getting hold of the application.

    Imports System
    Imports System.Reflection
    Imports System.Runtime.InteropServices
    Imports System.Text
    
    Namespace WordLateBinding
    
        <ComImport(), _
         InterfaceType(ComInterfaceType.InterfaceIsIUnknown), _
         Guid("00020400-0000-0000-C000-000000000046")> _
        Public Interface IDispatch
        End Interface
    
        Public Class Functions
    
            Private Declare Auto Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
            Private Declare Function AccessibleObjectFromWindow Lib "Oleacc.dll" (ByVal hwnd As Integer, ByVal dwObjectID As UInteger, ByVal riid() As Byte, ByRef ptr As IDispatch) As Integer
            Public Delegate Function EnumChildCallback(ByVal hwnd As Integer, ByRef lParam As Integer) As Boolean
            Public Declare Function EnumChildWindows Lib "User32.dll" (ByVal hWndParent As Integer, ByVal lpEnumFunc As EnumChildCallback, ByRef lParam As Integer) As Boolean
            Public Declare Auto Function GetClassName Lib "User32.dll" (ByVal hWnd As Integer, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer
    
            Public Shared Function EnumChildProc(ByVal hwndChild As Integer, ByRef lParam As Integer) As Boolean
                Dim buf As StringBuilder = New StringBuilder(128)
                Functions.GetClassName(hwndChild, buf, 128)
                If (buf.ToString = "_WwG") Then
                    lParam = hwndChild
                    Return False
                End If
    
                Return True
            End Function
    
            Public Function Main() As Microsoft.Office.Interop.Word.Application
    
                Dim hwnd As Integer = CType(FindWindow("OpusApp", Nothing), Integer)
                If (hwnd <> 0) Then
                    Dim hwndChild As Integer = 0
                    Functions.EnumChildWindows(hwnd, AddressOf EnumChildProc, hwndChild)
                    If (hwndChild <> 0) Then
                        Const OBJID_NATIVEOM As UInteger = 4294967280
                        Dim IID_IDispatch As Guid = New Guid("{00020400-0000-0000-C000-000000000046}")
                        Dim ptr As IDispatch
                        Dim hr As Integer = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray, ptr)
                        If (hr >= 0) Then
                            Dim wordApp As Object = ptr.GetType.InvokeMember("Application", BindingFlags.GetProperty, Nothing, ptr, Nothing)
                            Return wordApp
                        Else
                            Return Nothing
                        End If
                    Else
                        Return Nothing
                    End If
                Else
                    Return Nothing
                End If
    
            End Function
    
        End Class
    
    End Namespace
    


    Best Regards Peter Karlström

    Monday, January 15, 2018 9:30 AM

All replies

  • Hi Peter

    It's possible to start the Word application using a Command Line, rather than the New keyword. When you use a Command Line, you can add the switches, such as /a. The tricky part, of course, is using the instance of Word you start in this manner as a COM object.

    I haven't tried it, but I believe the code in Paul's answer in this link addresses the question, even though the topic is not Word: https://social.msdn.microsoft.com/Forums/vstudio/en-US/a10c6bcd-4745-4e07-a02e-3d98f5b6e941/display-all-open-excel-workbooks-in-a-listbox-multiple-processes

    For starting the process and getting its WindowTitle, something like:

    //Use ProcessStartInfo to create a new instance using 
    //commandline arguments
     ProcessStartInfo psi = new ProcessStartInfo("winword.exe", "/w/a");
     Process wdProc = Process.Start(psi);
     wdID = wdProc.Id;
     wdTitle = wdProc.MainWindowTitle;
     Timer t = new Timer();
     t.Start();
     while (wdTitle.Length <= 0 && t.Interval < 10)
     {
         wdTitle = wdProc.MainWindowTitle;
     }
     t.Stop();
     t = null;


    Cindy Meister, Office Developer/Word MVP, <a href="http://blogs.msmvps.com/wordmeister"> my blog</a>

    Thursday, January 11, 2018 4:06 PM
    Moderator
  • Hi Cindy

    Thank you for your reply.

    I have seen this approach for starting Word earlier, but the problem percists in getting a handle to the started Word application for cast to Word.Application.

    The link you provided seems to list everything but my started Word application.
    What would the ClassName be for Microsoft Word 2013?


    Best Regards Peter Karlström

    Friday, January 12, 2018 12:13 PM
  • The class name for Word when working with the Windows API is: OpusApp

    Note that this is also used in the Windows Registry...


    Cindy Meister, Office Developer/Word MVP, <a href="http://blogs.msmvps.com/wordmeister"> my blog</a>

    Friday, January 12, 2018 6:33 PM
    Moderator
  • One way to handle finding a specific instance of Word (the one started with /a) is to have Word open a known document.  Word will register this document in the running object table.  By obtaining an interface pointer to this known document from the running object table one can then obtain the Word application object.  An example of this method using C++ code is in the VC++ forum at https://social.msdn.microsoft.com/Forums/en-US/ce0dfdc3-c120-46a1-a775-d1ce9d576d73/running-winwordexe-x-using-from-class-coledispatchdriver-the-method?forum=vcgeneral
    Friday, January 12, 2018 8:07 PM
  • Hi Cindy

    Now I got it. This is how it's solved in VB.NET.

    Here is the calling sub:

    Private Sub TestWord_Click(sender As Object, e As EventArgs) Handles btnTest.Click
    
        Dim startInfo As New ProcessStartInfo("Winword.exe", "/a")
        startInfo.WindowStyle = ProcessWindowStyle.Hidden
    
        Dim wrd As Word.Application = Nothing
        Dim wProc As System.Diagnostics.Process = System.Diagnostics.Process.Start(startInfo)
        wdID = wProc.Id
        Dim wdTitle As String = wProc.MainWindowTitle
    
        Dim t As Timer = New Timer
        t.Start()
    
        While ((wdTitle.Length <= 0) AndAlso (t.Interval < 10))
            wdTitle = wProc.MainWindowTitle
        End While
    
        t.Stop()
        t = Nothing
    
        Dim cls As New WordLateBinding.Functions
        Threading.Thread.Sleep(500)
        wrd = cls.Main()
        If Not wrd Is Nothing Then
            'Do word stuff
            Dim aDoc As Word.Document = wrd.ActiveDocument
            Dim aSel As Word.Selection = aDoc.ActiveWindow.Selection
            aSel.TypeText("Hello World")
            wrd.Visible = True
        Else
            MsgBox("Word application couold not be found", MsgBoxStyle.Critical + MsgBoxStyle.OkOnly, "Testapplication Word")
        End If
    
    End Sub

    And here is the class used for getting hold of the application.

    Imports System
    Imports System.Reflection
    Imports System.Runtime.InteropServices
    Imports System.Text
    
    Namespace WordLateBinding
    
        <ComImport(), _
         InterfaceType(ComInterfaceType.InterfaceIsIUnknown), _
         Guid("00020400-0000-0000-C000-000000000046")> _
        Public Interface IDispatch
        End Interface
    
        Public Class Functions
    
            Private Declare Auto Function FindWindow Lib "user32.dll" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
            Private Declare Function AccessibleObjectFromWindow Lib "Oleacc.dll" (ByVal hwnd As Integer, ByVal dwObjectID As UInteger, ByVal riid() As Byte, ByRef ptr As IDispatch) As Integer
            Public Delegate Function EnumChildCallback(ByVal hwnd As Integer, ByRef lParam As Integer) As Boolean
            Public Declare Function EnumChildWindows Lib "User32.dll" (ByVal hWndParent As Integer, ByVal lpEnumFunc As EnumChildCallback, ByRef lParam As Integer) As Boolean
            Public Declare Auto Function GetClassName Lib "User32.dll" (ByVal hWnd As Integer, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer
    
            Public Shared Function EnumChildProc(ByVal hwndChild As Integer, ByRef lParam As Integer) As Boolean
                Dim buf As StringBuilder = New StringBuilder(128)
                Functions.GetClassName(hwndChild, buf, 128)
                If (buf.ToString = "_WwG") Then
                    lParam = hwndChild
                    Return False
                End If
    
                Return True
            End Function
    
            Public Function Main() As Microsoft.Office.Interop.Word.Application
    
                Dim hwnd As Integer = CType(FindWindow("OpusApp", Nothing), Integer)
                If (hwnd <> 0) Then
                    Dim hwndChild As Integer = 0
                    Functions.EnumChildWindows(hwnd, AddressOf EnumChildProc, hwndChild)
                    If (hwndChild <> 0) Then
                        Const OBJID_NATIVEOM As UInteger = 4294967280
                        Dim IID_IDispatch As Guid = New Guid("{00020400-0000-0000-C000-000000000046}")
                        Dim ptr As IDispatch
                        Dim hr As Integer = AccessibleObjectFromWindow(hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray, ptr)
                        If (hr >= 0) Then
                            Dim wordApp As Object = ptr.GetType.InvokeMember("Application", BindingFlags.GetProperty, Nothing, ptr, Nothing)
                            Return wordApp
                        Else
                            Return Nothing
                        End If
                    Else
                        Return Nothing
                    End If
                Else
                    Return Nothing
                End If
    
            End Function
    
        End Class
    
    End Namespace
    


    Best Regards Peter Karlström

    Monday, January 15, 2018 9:30 AM
  • Super, that you were able to work it out :-)! Thanks so much for posting it...

    Cindy Meister, Office Developer/Word MVP, <a href="http://blogs.msmvps.com/wordmeister"> my blog</a>

    Monday, January 15, 2018 3:13 PM
    Moderator