0

I have a C# application that exports a lot of data to Excel. I leave the Excel app open so the user can decide what they want to do with it. However, when Excel is shut down (directly from within Excel), there is still an Excel process hanging around that has to be manually killed.

I guess a solution is to save the data in Excel and shut it down from within my code using Marshal thus leaving no Excel apps hanging around, but my users want the file open to them when they export data.

Has anyone found a way around the zombie processes?

John
  • 195
  • 1
  • 3
  • 17
  • 2
    Do your program need to do anything after export the report? If no, try to export the excel, close the excel formally, and then launch the file (example: [Link](http://stackoverflow.com/a/1283593/1050927)), so you don't need to take care when user close the excel, and no zombie process left. – Prisoner Oct 28 '16 at 06:18
  • 1
    It's unfortunately a well known problem with Excel. I've found no bulletproof solution, but it seems to help to set all object returned from the Excel api to null when no more used. You could maybe close all Excel processes through marshal and then reopen one instance via Process.Start with the handled file as argument, but that may cause other Excel instances opened by the user to be unexpectedly closed as well? –  Oct 28 '16 at 06:29
  • Alex / Henrik - that looks like the best way forward. When exporting data, I created the Excel app with visible set to false - sometimes the user was interrupting the data dump and screwing things up. So, I could save the file, kill the Excel process and use Process.Start to fire it up again and they'd never know! – John Oct 28 '16 at 06:58
  • 1
    Alex / Henrik - your combined solution worked. When the user quits out of my application, all outstanding Excel processes disappear. – John Oct 28 '16 at 13:05

1 Answers1

1

Once I made the class below. It's a while ago and I haven't used it recently so don't hang me if it doesn't work. Feel free to use, change, be inspired or neglect it as you see fit.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Excel = Microsoft.Office.Interop.Excel;

namespace ExcelHandlingTest
{
  public class ExcelManager : IDisposable
  {
    Excel.Application m_excelApp = null;
    Process m_excelProcess = null;
    bool m_closeOnDispose = false;
    bool m_allowKillAll = false;

    public ExcelManager(bool useExistingInstance, bool closeOnDispose, bool visible, bool allowKillAll = false)
    {
      m_closeOnDispose = closeOnDispose;
      m_allowKillAll = allowKillAll;

      if (useExistingInstance)
      {
        try
        {
          m_excelApp = Marshal.GetActiveObject("Excel.Application") as Excel.Application;
        }
        catch (COMException ex)
        {
          m_excelApp = new Excel.Application();
        }
      }
      else
      {
        m_excelApp = new Excel.Application();
      }
      if (m_excelApp == null)
        throw new Exception("Excel may not be present on this machine");

      m_excelApp.Visible = visible;

      SetExcelProcess();
    }

    public Excel.Application Excel
    {
      get
      {
        return m_excelApp;
      }
    }

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

    private void SetExcelProcess()
    {
      uint processId = 0;
      GetWindowThreadProcessId((IntPtr)m_excelApp.Hwnd, out processId);
      m_excelProcess = Process.GetProcessById((int)processId);
    }

    private static List<int> GetExcelProcessIds()
    {
      return GetAllExcelProcesses().Select(p => p.Id).ToList();
    }

    public static void KillAllExcelProcesses()
    {
      foreach (Process p in GetAllExcelProcesses())
      {
        p.Kill();
      }
    }

    public static List<Process> GetAllExcelProcesses()
    {
      return (from p in Process.GetProcessesByName("Excel")
              where Path.GetFileName(p.MainModule.FileName).Equals("Excel.exe", StringComparison.InvariantCultureIgnoreCase) &&
              p.MainModule.FileVersionInfo.CompanyName.Equals("Microsoft Corporation", StringComparison.InvariantCultureIgnoreCase)
              select p).ToList();
    }

    public event ExitStatusEventHandler ExitStatus;
    private void OnExitStatus(string status)
    {
      if (ExitStatus != null)
        ExitStatus(this, status);
    }

    public void Dispose()
    {
      if (m_excelProcess != null)
        OnExitStatus(string.Format("Process ID: {0}", m_excelProcess.Id));

      if (m_closeOnDispose)
      {
        if (m_excelApp != null)
          m_excelApp.Quit();
        m_excelApp = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Thread.Sleep(50);

        if (m_excelProcess != null)
        {
          List<Process> excelProcs = GetAllExcelProcesses();
          foreach (Process ep in excelProcs)
          {
            if (ep.MainWindowHandle == m_excelProcess.MainWindowHandle)
            {
              ep.Kill();
              ep.WaitForExit();
              OnExitStatus("Exit by Kill");
              m_excelProcess = null;
              return;
            }
          }
        }

        if (m_allowKillAll)
        {
          List<Process> excelProcs = GetAllExcelProcesses();
          if (excelProcs != null && excelProcs.Count > 0)
          {
            KillAllExcelProcesses();
            OnExitStatus("Exit by Kill All");
            m_excelProcess = null;
            return;
          }
        }

        OnExitStatus("Exit by Quit");
        m_excelProcess = null;
      }
    }
  }

  public delegate void ExitStatusEventHandler(ExcelManager sender, string status);
}

Use case:

  using (ExcelManager man = new ExcelManager(true, true, true))
  {
    var excel = man.Excel;
    // do stuff..
  }