12

How do I get the Process ID from a running object?

Dim xlApp As Object  = CreateObject("Excel.Application")

I need to use late binding because I can't guarantee which version I will get so using Microsoft.Office.Interop.Excel won't work.

'do some work with xlApp

xlApp.Quit
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)
xlApp = nothing

At this point Excel is still running in the background. I am familiar with all the recommendations to use variables and release them then use: System.Runtime.InteropServices.Marshal.ReleaseComObject(o). This does not work reliably. The work that I'm doing is very complicated. I use for each loops etc using multiple files. It is impossible to release all of the resources in Excel. I need a better option.

I'd like to use Process.Kill on Excel but I don't know how to get the process from the xlApp object. I don't want to kill all Excel processes because the user might have a workbook open.

I tried using Dim xProc As Process = Process.Start(ExcelPath) then using xProc.Kill() That works sometimes except it's a little tricky to get the correct Excel object using XLApp = GetObject("Book1").Application or XLApp = GetObject("", "Excel.Application") if the user already has Excel windows open. I need a better option.

I can't use GetActiveObject or BindToMoniker to get the Excel object because they only work with work when using early binding. E.g. Microsoft.Office.Interop.Excel

How do I get the Process ID from a running object?

Edit: Actually I'm not really interested in a rehash on how to get Excel to nicely exit. Many other questions have addressed that. here and here I just want to kill it; cleanly, precisely and directly. I want to kill the exact process that I started and no other.

Community
  • 1
  • 1
D_Bester
  • 5,723
  • 5
  • 35
  • 77
  • *This does not work reliably* [Are you leaking COM objects by using more than one `.`?](http://stackoverflow.com/questions/2191489/releasing-temporary-com-objects) – ta.speot.is Jul 20 '13 at 06:39
  • This is in "not an answer" territory, but have you considered [EPPlus](http://epplus.codeplex.com/)? It has good support for Excel's features and doesn't leave a process dangling the background [among all the other bad things that the Office Automation interfaces bring](http://support.microsoft.com/kb/257757). – ta.speot.is Jul 20 '13 at 06:44
  • @ta.speot.is It is not always realistically possible to declare and release all COM objects in Excel. I spent quite a bit of time trying that on my project. Not feasible. – D_Bester Jul 20 '13 at 06:48
  • This question: http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects is the best rundown of all the issues with getting Excel to quit. Great resource. I used all of their recommendations but in the end killing the process was the easiest and the most reliable. – D_Bester Jul 20 '13 at 06:56
  • You set xlApp = nothing before calling ReleaseComObject(xlApp). How could this have any effect? xlApp.Quit should do the job, including getting Excel out of memory without having to resort to esoteric methods such as killing the excel process. I think the right qustion to ask is why Excel does not quit after calling the Quit method. – Tarik Jul 20 '13 at 07:42
  • @Tarik Obviously you haven't tackled this question in depth. Have you checked out: http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects The question is no longer why but what to do about it. – D_Bester Jul 20 '13 at 07:54
  • 2
    If your code isn't releasing all of the Excel references, *fix your code* instead of trying to find a hack to avoid doing so. It is always "realistically possible" to properly release all COM objects. I've done it for years, in several different languages. If you write proper code, it's not difficult at all. If you don't, no amount of kludgy, hacky avoidance techniques is going to work properly. – Ken White Jul 21 '13 at 00:21
  • @D_Bester: and http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects has not answered your question yet? – Tarik Jul 21 '13 at 04:37
  • @KenWhite Actually I tried hard. I spent hours declaring and releasing ALL Excel objects. When I was done, Excel still didn't quit. Theoretical best way doesn't trump "Just get it done." It is Excel that is kludgy, not my code. – D_Bester Jul 21 '13 at 13:45
  • @Tarik Actually that website gave a lot of answers, but what I was really looking for here was a way to cleanly kill the exact Excel process that my code started. I was not looking for a rehash on what is the best way to nicely ask Excel to please would you if you don't mind could you exit. No, I just want to kill it. I want to see blood. – D_Bester Jul 21 '13 at 14:04

6 Answers6

13

Actually never mind; I figured it out. This is a very clean, precisely targeted solution that kills the exact process that was started. It doesn't interfere with any other process or file that the user might have open. In my experience killing the process after closing files and quitting Excel is the fastest and easiest way to deal with Excel. Here is a knowledge Base article describing the problem and Microsoft's recommended solution.

Please note that this solution does NOT kill the Excel application. It only kills the empty process shell if any pointers have not been properly disposed. Excel itself DOES actually quit when we call xlApp.quit(). This can be confirmed by trying to attach the running Excel application which will fail because Excel is not running at all.

Many people don't recommend killing the process; See How to properly clean up Excel interop objects and Understanding Garbage Collection in .net

On the other hand many people don't recommend using GC.Collect. See What's so wrong about using GC.Collect()?

Be sure to Close any open workbooks, Quit the application, Release the xlApp object. Finally check to see if the process is still alive and if so then kill it.

Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, _
              ByRef lpdwProcessId As Integer) As Integer

Sub testKill()

    'start the application
    Dim xlApp As Object = CreateObject("Excel.Application")

    'do some work with Excel

    'close any open files

    'get the window handle
    Dim xlHWND As Integer = xlApp.hwnd

    'this will have the process ID after call to GetWindowThreadProcessId
    Dim ProcIdXL As Integer = 0

    'get the process ID
    GetWindowThreadProcessId(xlHWND, ProcIdXL)

    'get the process
    Dim xproc As Process = Process.GetProcessById(ProcIdXL)

    'Quit Excel
    xlApp.quit()

    'Release
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)

    'set to nothing
    xlApp = Nothing

    'kill it with glee
    If Not xproc.HasExited Then
        xproc.Kill()
    End If

End Sub

Once I realized that I could get the window handle from Excel, then I just needed the function to get the process ID from the window handle. Hence GetWindowThreadProcessId If anyone knows a vb.net way to get that I would be grateful.

Community
  • 1
  • 1
D_Bester
  • 5,723
  • 5
  • 35
  • 77
  • 5
    This is **completely** the wrong way to solve this problem. Read [this answer](http://stackoverflow.com/a/17131389/17034) to understand what's going on. – Hans Passant Jul 21 '13 at 00:14
  • @HansPassant Very interesting piece that. It illuminates the GC garbage collection. But many people have said that using GC.Collect(); GC.WaitForPendingFinalizers(); is actually an expensive operation. I'm not sure why killing the Excel process after closing the files and quitting the application is worse than GC.Collect. I would be glad for an answer on that. – D_Bester Jul 21 '13 at 13:52
  • @HansPassant Thanks for your input on this. I edited my answer to include some links to the recommended ways of dealing with Excel. – D_Bester Jul 22 '13 at 06:17
  • 3
    http://stackoverflow.com/questions/21310221/how-to-debug-slow-office-application-interop-constructor – Hans Passant Feb 03 '14 at 01:06
6

Using Marshal.ReleaseComObject() or killing the Excel.exe process are ugly, error prone and unnecessary band-aids for this problem. And highly detrimental in the long run, this question shows what can happen. The proper way to do it is by calling GC.Collect(), but read this answer to understand why this tends to not work when you debug your program.

The workaround is simple, you just need to make sure that you call GC.Collect() in a different method. Which ensures that your Excel object references are no longer in scope. So the rough outline of a program that does this right would be:

Sub Main()
    DoOfficeStuff()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    '' Excel.exe will now be gone
    '' Do more work
    ''...
End Sub

Sub DoOfficeStuff()
    Dim xlApp As Object = CreateObject("Excel.Application")
    '' etc..
End Sub
Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Personally I wouldn't bother with the Office Interop stuff unless EPPlus couldn't manipulate the spreadsheet in some special Excel-only way. – ta.speot.is Jul 21 '13 at 03:07
  • @Hans Passant: Wouldn't the GC eventually garbage collect Excel (although it would do it on its own terms)? In that case is there any use of forcing it by using GC.Collect? How true is this statement: Excel is not released if "invoking at least one member of a COM object without assigning it to a variable". See http://stackoverflow.com/questions/158706/how-to-properly-clean-up-excel-interop-objects. – Tarik Jul 21 '13 at 04:58
  • It certainly will. The chronic problem is that programmers don't actually like the way garbage collectors work. They do unwise things, like stop allocating memory and still expecting the GC to collect garbage. – Hans Passant Jul 21 '13 at 08:30
  • 1
    @HansPassant I see your point on that except that my own answer on how to kill Excel is not error prone; it works quite cleanly. So I don't think it is ugly, error prone or an unnecessary band-aid. Rather IMHO it's actually beautiful, precise and cures the problem of Excel not exiting. – D_Bester Jul 21 '13 at 13:58
  • @HansPassant In any case this answer doesn't even address the question; it just tries to give another method to force Excel to exit. The question I actually posed was this: How do I get the Process ID from a created “Excel.Application” object? – D_Bester Jul 21 '13 at 14:00
  • 2
    Sorry about that, but pointing out that a programmer is using the wrong solution to solve his problem is considered a legitimate answer to a question at SO. The bigger idea is that solutions are not intended to be only useful to the asker, anybody that googles this question some day might greatly prefer not intentionally killing a process. As well they should. Surely you can see the sense in that? You can downvote an answer to make you feel better about it. – Hans Passant Jul 21 '13 at 14:05
  • Here is a question about GC.Collect and whether it should be used. http://stackoverflow.com/questions/118633/whats-so-wrong-about-using-gc-collect – D_Bester Jul 22 '13 at 00:27
2
Public Declare Function GetWindowThreadProcessId Lib "user32" _
  (ByVal hwnd As Long, _
   ByRef lpdwProcessId As Long) As Long

Function KillProcess(hwnd As Long)
  Dim CurrentForegroundThreadID As Long
  Dim strComputer As String
  Dim objWMIService
  Dim colProcessList
  Dim objProcess
  Dim ProcIdXL As Long

  ProcIdXL = 0
  CurrentForegroundThreadID = GetWindowThreadProcessId(hwnd, ProcIdXL)

  strComputer = "."

  Set objWMIService = GetObject _
  ("winmgmts:\\" & strComputer & "\root\cimv2")
  Set colProcessList = objWMIService.ExecQuery _
    ("Select * from Win32_Process Where ProcessID =" & ProcIdXL)
  For Each objProcess In colProcessList
    objProcess.Terminate
  Next

End Function


KillProcess (ExcelApplication.hwnd)
Jakuje
  • 24,773
  • 12
  • 69
  • 75
ajsco
  • 21
  • 2
0

For C# use:

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    uint iProcessId = 0;

    //Get the process ID of excel so we can kill it later.
    GetWindowThreadProcessId((IntPtr)ExcelObj.Hwnd, out iProcessId);

    try
    {
        Process pProcess = Process.GetProcessById((int)iProcessId);
        pProcess.Kill();
    }
    catch (System.Exception)
    {
        //just ignore any failure.
    }
JimMoore
  • 31
  • 2
0

I found a workaround (VB source):

Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer

First Marshal.ReleaseComObject() all more detailed objects and set them to Nothing.

Bring Excel window to top after closing last workbook and then wait 1000 ms (good value for working chance):

exlBook.Close()
Dim exlProcId As Integer = Nothing
GetWindowThreadProcessId(exlApp.Hwnd, exlProcId)
AppActivate(exlProcId)
Threading.Thread.Sleep(1000)
Marshal.ReleaseComObject(exlBook)
exlBook = Nothing

At last:

exlApp.Quit()
exlApp = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()

I found it out when I stopped the programme after closing the working book, then opened the Excel window, then continued the programme and the Excel process was closed.

Kind regards: Thorsten, Hanover, Germany

0

I ran into this problem when developing an unattended app that creates 50 to 100 Excel workbooks each day and emails them. No matter what I did, an instance of Excel hung around in Task Manager, sometimes even after GC.WaitForPendingFinalizers().

As the app is unattended, I can't risk Excel eating up the machine, so I combined all the approaches into:

Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer

        Dim XLApp As Excel.Application
        Dim XLHwnd As Integer
        Dim XLProcID As Integer
        Dim XLProc As Process

        XLApp = New Excel.Application

        XLHwnd = XLApp.Hwnd
        GetWindowThreadProcessId(CType(XLHwnd, IntPtr), XLProcID)
        XLProc = Process.GetProcessById(XLProcID)

        DoStuffWithExcel()

        Try
            XLApp.Quit()
            System.Runtime.InteropServices.Marshal.ReleaseComObject(XLApp)
            XLApp = Nothing
            GC.Collect()
            GC.WaitForPendingFinalizers()

            If Not XLProc.HasExited Then
                XLProc.Kill()
            End If
        Catch ex As Exception
            XLApp = Nothing
        Finally
            GC.Collect()
        End Try

I try to exit gracefully, but if it fails, I kill the process, because I have to!

So far, it's behaved itself ;-)

Yowser
  • 36
  • 1
  • 4