• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 2384
  • Last Modified:

How to get the process ID for an Excel automation?

I have an application that reads the contents of Excel files uploaded by users. After getting the data, I need to identify the process ID associated with the Excel instance to be able to kill the process. The GetWindowThreadProcessId function seems to do the trick on the development environment (OS is Windows XP Professional) and I am getting the correct process ID but when I run it on a Windows 2003 Server, I get a Win32 error saying that the window handle that I passed is invalid. Please refer to the code.
Microsoft.Office.Interop.Excel.Application Excel = new Microsoft.Office.Interop.Excel.Application();
 
      int ExcelPId = 0;
 
      GetWindowThreadProcessId(Excel.Hwnd, ref ExcelPId);
 
      if (ExcelPId == 0)
        Response.Write((new Win32Exception()).Message);

Open in new window

0
elvin226
Asked:
elvin226
  • 13
  • 10
1 Solution
 
elvin226Author Commented:
I've tried impersonating an account with admin rights before calling Excel but I'm still getting an invalid window handle error.
0
 
Bob LearnedCommented:
If you are having to kill the process, then you are not closing Excel correctly.  There are different approaches to reading Excel files, but using Excel automation is not the most efficient way.  
0
 
elvin226Author Commented:
Actually, there are other things that need to be done besides reading files. The program also generates Excel files and I need to:
  • Dynamically create ranges of values for lists that validate user inputs
  • Dynamically modify the protection settings to a range of cells and finally, lock the worksheet to prevent unauthorized editing. The random password is sent to selected users while giving others a read-only access
What other approaches are better than automation?
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
Bob LearnedCommented:
OLE DB is a great way to read Excel, but if you are really talking about automating Excel, then automation is still the way to go.  Are you using System.Runtime.InteropServices.Marshal.ReleaseComObject?  .NET does not use reference counting the same way that COM does.  Using instances of COM objects increments there reference counts, but nothing decrements the counts, so they are left in memory, and Excel won't close, until that memory is released.
0
 
elvin226Author Commented:
I have yet to try that. Thanks.
I've tried the following code. The problem is if I do a series of automation calls, at least one of the processes is left running.

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    GC.WaitForPendingFinalizers();

Open in new window

0
 
Bob LearnedCommented:
If you are creating instances of Excel.Range objects, for example, then you need to release those objects also.
0
 
elvin226Author Commented:
Oh I see. But in a single run, I only create two instances: Excel.Application and Excel.Workbook. From these, I access the worksheets, ranges and cells as collections. Is it enough to release just these two instances?
Excel.Application ExcelApp = new Excel.Application();
Excel.Workbook WBook = ExcelApp.Workbooks.Open(...);
((Excel.Range)((Excel.Worksheet)WBook.Worksheets[1]).Cells[1, 1]).Value2 = "Some value";
WBook.Close(...);
ExcelApp.Quit();
Marshal.ReleaseComObject(WBook);
Marshal.ReleaseComObject(ExcelApp);

Open in new window

0
 
elvin226Author Commented:
Or I can use only the Excel.Application so that I only have one instance to free:
Excel.Application ExcelApp = new Excel.Application();
 
((IExcel.Range)((IExcel.Worksheet)((IExcel.Workbook)ExcelApp.Workbooks[1]).Worksheets[1]).Cells[1, 1]).Value2 = "Some value";
 
((IExcel.Workbook)Excel.Workbooks[1]).Close(...);
 
ExcelApp.Quit();
Marshal.ReleaseComObject(ExcelApp);

Open in new window

0
 
Bob LearnedCommented:
No, any implicit object needs to be released also.  That is the ugly nature of working with COM objects in .NET.

Example:


using Microsoft.Office.Core;
using System.IO;
using System.Runtime.InteropServices;
 
public enum ExportImageType
{
    JPG,
    BMP,
    PNG,
    GIF
}
 
public class PowerPointExporter
{
 
    public static void ExportPresentation(string fileName, string outputPath, ExportImageType imageType, int width, int height)
    {
 
        if (!File.Exists(fileName))
            throw new FileNotFoundException(fileName);
 
        if (!Directory.Exists(outputPath))
            throw new DirectoryNotFoundException(outputPath);
 
        PowerPoint.Application app = new PowerPoint.Application();
        app.Visible = Office.MsoTriState.msoTrue;
 
        PowerPoint.Presentation presentation = app.Presentations.Open(fileName, Office.MsoTriState.msoTrue,
            Office.MsoTriState.msoFalse, Office.MsoTriState.msoTrue);
 
        foreach (PowerPoint.Slide slide in presentation.Slides)
        {
            string name = (slide.Name.Trim().Length == 0) ? slide.SlideIndex.ToString() : slide.Name;
            string outputFileName = Path.Combine(outputPath, name) + "." + imageType.ToString();
            slide.Export(outputFileName, imageType.ToString(), width, height);
            Marshal.ReleaseComObject(slide);
        }
 
        Marshal.ReleaseComObject(presentation);
        Marshal.ReleaseComObject(app);
    }
 
}

Open in new window

0
 
elvin226Author Commented:
When you say implicit instance, are you referring to the casts that I made? For example, in the expression (Excel.Workbook)ExcelApp.Workbooks[1] do I have an implicit instance of Excel.Workbook? If this is the case then how can I release an implicit instance if I don't have the object to pass as an argument to Marshal.ReleaseComObject?
I can't seem to find any implicit instance in your example.
0
 
Bob LearnedCommented:
Implicit just means that you get an instance without getting a reference:

1) Explicit
 foreach (PowerPoint.Slide slide in presentation.Slides)

...

           Marshal.ReleaseComObject(slide);
 
2) Implicit

(Excel.Workbook)ExcelApp.Workbooks[1]

3) Explicit

Excel.Workbook workBook = (Excel.Workbook)ExcelApp.Workbooks[1];

...

Marshal.ReleaseComObject(workBook);

0
 
elvin226Author Commented:
Thanks for your patience, though I have to ask again, how can I release an implicit instance?
0
 
elvin226Author Commented:
Ok, I tried the Marshal.ReleaseComObject. It worked on Windows XP Professional but not on Windows 2003 server. Any solution I found doesn't seem to work when it comes to Windows 2003 sever. What am I missing?
0
 
Bob LearnedCommented:
"It worked on Windows XP Professional but not on Windows 2003 server"

Does that mean that it still left Excel open on Windows 2003 Server, or you got an exception?

"how can I release an implicit instance?"

You can't since you don't have a reference to it on the .NET side, which you need to pass to Marshal.ReleaseComObject to decrement the reference counter.
0
 
elvin226Author Commented:
On Windows 2003 server, Marshal.ReleaseComObject is executed without any exceptions but it had no effect either. The process is still left running.
Going back to my original question, on Windows XP, when I pass the Hwnd of the Excel instance as a parameter to the API GetWindowThreadProcessId, I can get the corresponding process ID, but on Windows 2003 server I get a Win32 error saying that the window handle is invalid.
Are there some settings that need to be considered to make it work on Windows 2003?
0
 
Bob LearnedCommented:
I tried, but if you must kill the process, then you should be able to do it like this (no promises with Windows 2003 server):


            foreach (Process process in Process.GetProcessesByName("Excel"))
            {
                process.Kill();
            }

Open in new window

0
 
elvin226Author Commented:
It's not really a question of how to kill a process; the problem is identifying the correct process to kill. I can't kill all instances of Excel that I find on the server because there might be multiple users generating the file at the same time.
As I said from the start of this thread, I already have it working with Windows XP. I can get the correct process ID using the GetWindowThreadProcessId and the window handle (Excel.Hwnd)
For some reason, the GetWindowThreadProcessId works on Windows XP but not on Windows 2003 server.
GetWindowThreadProcessId(Excel.Hwnd, ref ExcelPId) ; //OK on Windows XP
GetWindowThreadProcessId(Excel.Hwnd, ref ExcelPId) ; /*Not OK on Windows 2003 server; it says that Excel.Hwnd is an invalid window handle*/
0
 
elvin226Author Commented:
In the first place, the reason why I'm trying to get the process ID using the GetWindowThreadProcessId function is so that I can this:
GetWindowThreadProcessId(Excel.Hwnd, ref ExcelPId);
 
foreach (Process process in Process.GetProcessesByName("Excel"))
  if (process.Id == ExcelPId)
  {
    process.Kill();
    break;
  }

Open in new window

0
 
Bob LearnedCommented:
ASP.NET is tricky when working with COM objects in the first place, and Windows 2003 Server only complicates the issue because of its tighter security model.  I would have no idea why GetWindowThreadProcessId wouldn't work.

If you need to compare Excel.Hwnd against a process handle, then the Process class has the Handle property, which is an IntPtr that you can convert to an integer with ToInt32().
0
 
elvin226Author Commented:
The process handle value is different from Hwnd maybe because the latter is a window handle; the process handle is not window handle.
The following solution works so far for Windows 2003 server:  I try to impersonate the user, create the instance (the associated process is now owned by the impersonated user and not the iusr account). Later I query all Excel processes and which ever is owned by the impersonated user gets killed.
The problem is, if I'm going to use this same solution for future applications, then the Excel process, even if owned by the user, may no longer be exclusive; I have no way of knowing which process is initiated from which application.
0
 
Bob LearnedCommented:
Ok, so we have come around full circle, and I believe that killing the process is not the "ideal" solution, and you should figure out how to close Excel cleanly, and not have the process remain open after you quit the application.
0
 
elvin226Author Commented:
Yes, I agree. I hope someone could tell us how to do that.
0
 
Bob LearnedCommented:
I attempted to tell you how to get references to objects used with Excel, and how to use Marshal.ReleaseComObject, but that didn't seem to help your cause.

Maybe a word from a PHD might help cement your issues.

Create Dynamic ASP.NET Excel Workbooks In C#
http://www.eggheadcafe.com/articles/20021012.asp
0

Featured Post

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

  • 13
  • 10
Tackle projects and never again get stuck behind a technical roadblock.
Join Now