Home » excel » Wait for Excel Interlop to Exit in C#

Wait for Excel Interlop to Exit in C#

Posted by: admin April 23, 2020 Leave a comment

Questions:

I am working on a tool which will automate a couple of actions for me. One of these actions is download an excel file, run a macro on it and then mail the file. In some cases i want the tool to just run the excel macro, then after x seconds exit excel and mail the file. This all works fine since i can just wait for fixed period of time. However, in some cases i want the to be able to check and change the data which has been retrieved by the excel macro. When this is the case i want to keep excel open until the used manually closes excel. When i detect excel is no longer opened i want to mail the file.

This last case gives me some trouble. Because i use the excel interlop to open excel, i cannot i am not able to use the WaitForExit() like i can with a normal process. When i manually close excel the process also keeps running in the process explorer.

I have searched for solutions on the internet but none of them really work. Is there any way i can achieve this in a simple way?

UPDATE
Thanks a lot for your reply, It really helped me. Waiting for excel now works correcly. I used the following code:

if (Settings.ExitExcel)
{
  System.Threading.Thread.Sleep(Settings.ExcelTimeout * 1000);

  //Close Excel
  excelWorkbook.Close();
  excelApp.Quit();
}
else
{
  Excel.AppEvents_WorkbookBeforeCloseEventHandler Event_BeforeBookClose;

  Event_BeforeBookClose = new Excel.AppEvents_WorkbookBeforeCloseEventHandler(WorkbookBeforeClose);
  excelApp.WorkbookBeforeClose += Event_BeforeBookClose;

  //Wait until excel is closed
  while (!isClosed)
  {
    Thread.Sleep(1000);
  }

  //Show message               
  MessageBox.Show("excel closed");
}

//Clean up excel.
Marshal.FinalReleaseComObject(excelSheets);
Marshal.FinalReleaseComObject(excelWorksheet);
Marshal.FinalReleaseComObject(excelWorkbook);
Marshal.FinalReleaseComObject(excelApp);
excelApp = null;
excelWorkbook = null;
excelWorksheet = null;
excelSheets = null;
GC.Collect();     

Only problem now is that excel still keeps running in the processes. Am i not closing it correctly?

How to&Answers:

You can do this by using the BeforeClose event. Basically, you need to do something along the following lines:

  1. Create a variable to track state i.e. If Excel is open or closed.
  2. Create an event handler for the WorkbookBeforeClose event.
  3. In the handler, check if workbook was closed and update the value of your state tracking variable accordingly.
  4. If the workbook is closed, perform your mail sending action.

Code can be something like this:

//Outside main
private static bool isClosed = false;

....

//Let's say your Workbook object is named book
book.WorkbookBeforeClose += new Excel.AppEvents_WorkbookBeforeCloseEventHandler(app_WorkbookBeforeClose);

if(isClosed)
sendMailMethod();

...

private static void app_WorkbookBeforeClose(Excel.Workbook wb, ref bool cancel)
    {
        closed = !cancel;
    }

This approach does not require you to kill the process first. However, you should release the COM objects and end the process once the Excel work is done.

EDIT: To close the process, try the Application.Quit() method.

Answer:

If you have not found a solution, here is what I’ve put together that works for me.

private async void OnOpenWorkSheetImportCommand(object sender, ExecutedRoutedEventArgs e)
{
    try
    {
        if (e.Parameter is IWorkSheetImportViewModel model)
        {
            if (WorksheetDictionaries.OfType<WorksheetDictionary>().FirstOrDefault(a => a.Header.Equals(model.Header)) is WorksheetDictionary worksheetDictionary)
            {
                Type officeType = Type.GetTypeFromProgID("Excel.Application", true);

                if (officeType != null)
                {
                    BeforeWorkbookDictionary = worksheetDictionary.WorkbookDictionary;
                    OpenInteropWorkbookDictionary(worksheetDictionary.WorkbookDictionary, worksheetDictionary, true);
                }
                else
                {
                    throw new ArgumentException("Microsoft Excel is not installed!");
                }
            }
        }
    }
    ...
    ..
    .
}

Calling OpenInteropWorkbookDictionary is where I setup Microsoft.Office.Interop.Excel

private void OpenInteropWorkbookDictionary(WorkbookDictionary workbookDictionary, WorksheetDictionary worksheetDictionary, bool activateWorksheet = false)
{
    if (ExcelApplication != null && ExcelApplication.Visible == true)
    {
        ExcelApplication.ActiveWindow.Activate();
        IntPtr handler = FindWindow(null, ExcelApplication.Caption);
        SetForegroundWindow(handler);
    }
    else
    {
        if (ExcelApplication != null) Marshal.FinalReleaseComObject(ExcelApplication);
        if (ExcelWorkbook != null) Marshal.FinalReleaseComObject(ExcelWorkbook);
        if (ExcelWorksheet != null) Marshal.FinalReleaseComObject(ExcelWorksheet);
        if (ExcelWorksheets != null) Marshal.FinalReleaseComObject(ExcelWorksheets);

        ExcelApplication = null;
        ExcelWorkbook = null;
        ExcelWorksheet = null;
        ExcelWorksheets = null;

        ExcelApplication = new Microsoft.Office.Interop.Excel.Application
        {
            // if you want to make excel visible to user, set this property to true, false by default
            Visible = true,
            WindowState = Microsoft.Office.Interop.Excel.XlWindowState.xlMaximized
        };

        ExcelApplication.WindowActivate += new Microsoft.Office.Interop.Excel.AppEvents_WindowActivateEventHandler(OnWindowActivate);
        ExcelApplication.WindowDeactivate += new Microsoft.Office.Interop.Excel.AppEvents_WindowDeactivateEventHandler(OnWindowDeactivate);

        ExcelApplication.WorkbookOpen += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookOpenEventHandler(OnWorkbookOpen);
        ExcelApplication.WorkbookActivate += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookActivateEventHandler(OnWorkbookActivate);
        ExcelApplication.WorkbookDeactivate += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookDeactivateEventHandler(OnWorkbookDeactivate);

        // open an existing workbook
        ExcelWorkbook = ExcelApplication.Workbooks.Open(BeforeWorkbookDictionary.FilePath, 0, false, 5, "", "", false, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
        ExcelWorkbook.BeforeClose += new Microsoft.Office.Interop.Excel.WorkbookEvents_BeforeCloseEventHandler(OnBeforeClose);
        ExcelWorkbook.AfterSave += new Microsoft.Office.Interop.Excel.WorkbookEvents_AfterSaveEventHandler(OnAfterSave);
        ExcelWorkbook.BeforeSave += new Microsoft.Office.Interop.Excel.WorkbookEvents_BeforeSaveEventHandler(OnBeforeSave);

        // SheetEvents
        ExcelWorkbook.SheetChange += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetChangeEventHandler(OnSheetChange);
        ExcelWorkbook.SheetSelectionChange += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetSelectionChangeEventHandler(OnSheetSelectionChange);
        ExcelWorkbook.NewSheet += new Microsoft.Office.Interop.Excel.WorkbookEvents_NewSheetEventHandler(OnNewSheet);
        ExcelWorkbook.SheetBeforeDelete += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetBeforeDeleteEventHandler(OnSheetBeforeDelete);
        ExcelWorkbook.SheetActivate += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetActivateEventHandler(OnSheetActivate);
        ExcelWorkbook.SheetDeactivate += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetDeactivateEventHandler(OnSheetDeactivate);

        //// get all sheets in workbook
        ExcelWorksheets = ExcelWorkbook.Worksheets;
    }

    if (activateWorksheet)
    {
        ExcelWorksheet = ExcelWorkbook.Sheets[worksheetDictionary.Name];
        ExcelWorksheet.Activate();
    }
}

TO NOTICE:

  1. ExcelWorkbook.BeforeClose is called first then,
  2. ExcelApplication.WorkbookDeactivate is called last before Excel is completely closed
  3. So in ExcelApplication.WorkbookDeactivate is where I setup and use Process to wait for it to terminate on its own naturally.
  4. Extra: I use Reactive to catch the OnClose Event.
  5. IMPORTANT NOTE: I close Excel when the user closes the Workbook!

So in OnWorkbookDeactivate:

private async void OnWorkbookDeactivate(Microsoft.Office.Interop.Excel.Workbook Wb)
{
    try
    {
        ProgressController = default(ProgressDialogController);
        if (ExcelApplication != null && ExcelApplication.Visible == true)
        {
            int excelProcessId = -1;
            GetWindowThreadProcessId(new IntPtr(ExcelApplication.Hwnd), ref excelProcessId);
            Process ExcelProc = Process.GetProcessById(excelProcessId);

            if (ExcelProc != null)
            {
                ExitedEventArgs = Observable.FromEventPattern<object, EventArgs>(ExcelProc, MethodParameter.Exited);
                DisposableExited = ExitedEventArgs.Subscribe(evt => OnExitedEvent(evt.Sender, evt.EventArgs));
                ExcelProc.EnableRaisingEvents = true;

                ProgressController = await _dialogCoordinator.ShowProgressAsync(this, string.Format("Workbook {0}", BeforeWorkbookDictionary.ExcelName), "Please wait! Processing any changes...");
                ProgressController.SetIndeterminate();

                ExcelApplication.Quit();

                if (ExcelApplication != null) Marshal.FinalReleaseComObject(ExcelApplication);
                if (ExcelWorkbook != null) Marshal.FinalReleaseComObject(ExcelWorkbook);
                if (ExcelWorksheet != null) Marshal.FinalReleaseComObject(ExcelWorksheet);
                if (ExcelWorksheets != null) Marshal.FinalReleaseComObject(ExcelWorksheets);

                ExcelApplication = null;
                ExcelWorkbook = null;
                ExcelWorksheet = null;
                ExcelWorksheets = null;

                ExcelProc.WaitForExit();
                ExcelProc.Refresh();
            }
        }
    }
    catch (Exception msg)
    {
    ...
    ..
    .
    }
}

I setup a couple things in-class to get all of this to work properly:

#region DllImports

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr hwnd, ref int lpdwProcessId);

#endregion

And:

#region RoutedEventArgs Properties

private IDisposable disposableClosingEvent;
public IDisposable DisposableClosingEvent
{
    get => this.disposableClosingEvent; set => SetProperty(ref disposableClosingEvent, value, nameof(DisposableClosingEvent));
}

private IObservable<EventPattern<object, EventArgs>> exitedEventArgs;
public IObservable<EventPattern<object, EventArgs>> ExitedEventArgs
{
    get => exitedEventArgs; set => SetProperty(ref exitedEventArgs, value, nameof(ExitedEventArgs));
}
private IDisposable disposableExited;
public IDisposable DisposableExited
{
    get => this.disposableExited; set => SetProperty(ref disposableExited, value, nameof(DisposableExited));
}

#endregion

And Finally: The OnExitedEvent to do some last minute processing.

private async void OnExitedEvent(object sender, EventArgs e)
{
    try
    {
        DisposableExited.Dispose();
        using (SpreadsheetDocument s = SpreadsheetDocument.Open(SelectedWorkbookDictionaryImport.FilePath, false))
        {
            foreach (Sheet workbookSheet in s.WorkbookPart.Workbook.Sheets)
            {
                if (WorkbookDictionaryImports.OfType<WorkbookDictionary>().FirstOrDefault(d => d.ExcelName.Equals(SelectedWorkbookDictionaryImport.ExcelName)) is WorkbookDictionary workbookDictionary)
                {
                    if (workbookDictionary.WorksheetDictionaryItems.OfType<WorksheetDictionary>().FirstOrDefault(d => d.Id.Equals(workbookSheet.Id)) == null)
                    {
                        Application.Current.Dispatcher.Invoke(() =>
                        {
                            WorksheetDictionary worksheetDictionary = new WorksheetDictionary()
                            {
                                WorksheetDictionaryId = Guid.NewGuid().ToString(),
                                IsSelected = false,
                                Id = workbookSheet.Id,
                                SheetId = workbookSheet.SheetId,
                                Name = workbookSheet.Name,
                                Header = workbookSheet.Name,
                                HeaderInfo = GetSheetStateValues(workbookSheet.State),
                                Code = GetSheetCodeValues(workbookSheet.Name.ToString()).ToString(),
                                Description = BeforeWorkbookDictionary.Subject,
                                WorkbookDictionary = BeforeWorkbookDictionary
                            };
                            workbookDictionary.WorksheetDictionaryItems.Add(worksheetDictionary);
                        });
                    }
                }
            }
        }
    }
    catch (Exception msg)
    {
    ...
    ..
    .
    }
    finally
    {
        if (ProgressController != null)
        {
            await ProgressController.CloseAsync();
            ProgressController = default(ProgressDialogController);
        }
    }
}

Hope this will help you and others, of coarse make changes to fit your processing needs.