I would like to build a list of all processes generated by events in a form and kill them all when the form closes. How do I code this?
When I worked with the Excel Interop we had several processes created by it, and we had problems if we had real Excel workbooks opened at the same time the program was running.
If you kill the process and your user has a workbook opened, you might close their Excel instead. I would suggest getting a list of the PIDs for the processes that have Excel in their name before opening the workbook in your program, then get the list again immediately after opening it, and by seeing what is new determine the PID of your process. When you're done with the workbook, kill the process with the PID you retrieved.
Edit: when you open the workbook in code, a new process is created. Before opening the workbook, you have a list of processes - let's say 10 (by usingpart of the code Harendra wrote). Then you open the workbook, and get the list again - and you'll have 11 processes (you might have more, but only one of those is Excel). By comparing the two lists, you get the ID of the new process, which is your opened workbook. You add to a list or processes opened by you, and when closing the program, kill all the processes from the list.
Try this...
Process[] procList = Process.GetProcesses();
for (int i = 0; i <= procList.Length - 1; i ++) {
string strProcName = procList[i].ProcessName;
string strProcTitle = procList[i].MainWindowTitle();
//check for your process name.. here i m checking excel process
if (strProcName.ToLower().Trim().Contains("excel")) {
procList[i].Kill();
}
}
In continuation with reply from Harendra, you can store all the process ID of process invoked by you ! and close them at the time of closing.
This statement is valid only if you are using Process.Start to start processes
Related
I have been asked to write a script to crawl through a load of folder locations and list out all the Excel spreadsheets that have connections to a set of SQL and other data sources that are due to be upgraded to a new server.
In order to do this, I need to open each file, then check the connections and return those that match the criterion set. All this happens fine until I hit any file where the end user has made a macro to run on open that refers to a non-existent file - As the C# script opens the file, the file presents the following message:
If I manually click "End", the script moves on to the next file and all is ok, but I would much rather avoid any user input and record the fact that there was a problem with the macro... How would I go about doing that?
I have set the Excel property "Disable all macros without notification" to true on the computer that will be running the script, using the same username as will run it, which I thought would prevent this kind of thing happening. I also open Excel with DisplayAlerts=false, so that isn't the problem...
I don't need to run the macro at all and would rather not..!
for context, the code snippet that opens each file looks like this:
var app = new Application
{
Visible = false,
DisplayAlerts = false,
ScreenUpdating = false
};
Workbook thisFile = null;
try
{
//send a false password to stop Excel asking for one - when it is wrong, the error will be caught.
thisFile = app.Workbooks.Open(file.FullName, ReadOnly: true, Password: "FakePassword");
foreach (WorkbookConnection connection in thisFile.Connections)
{
EDIT: It occurs to me that maybe I could do something with a timeout..? If there were some way to close the popup box from the script, that would do the job - I could just record that the timer expired in the output, which would be enough. So... alternatively is there a way to just close the box after it has popped up?
I have been able to disable startup macros when I open the workbook by holding down shift when opening the file.
I believe the interop way to handle this is to use the application AutomationSecurity property:
Excel.Application app = new Excel.Application();
app.Visible = true;
app.AutomationSecurity = Microsoft.Office.Core.MsoAutomationSecurity.msoAutomationSecurityForceDisable;
I tested this on a simple workbook that popped up a message box and put the current time in A1, and it seemed to work properly.
Excel.Workbook wb = app.Workbooks.Open("c:/cdh/foot.xlsm");
Default, the message box popped up and A1 had a value, and when I set it to disable neither happened.
Some programs (image programs such as Paint, text editors such as notepad and Wordpad,and others) open files, load the contents into memory, then release the file lock. Is there a way to tell if a program is using that file even though it's not locked?
For example, even if image1.bmp is open in Paint, my program can overwrite the copy of image1.bmp that's on the disk because the file isn't locked. Now the copy of image1.bmp that is open in Paint is different than the copy of image1.bmp that is on the disk.
My program is written in C#. I usually use this method for checking if a file is locked, but it won't work in the above case.
Is there a way to check if a file is in use?
Is there any solution to this?
"Now the copy of image1.bmp that is open in Paint" - here's your mistake - the file is no longer open in Paint. It was opened, read, and then closed. Paint does not keep the file open at all - it only has a COPY of its contents in RAM memory. To put it in another way - the fact that you see a picture in MS Paint doesn't mean the file is open.
It is comparable to loaning a document to someone, then he makes a photocopy and returns it - that person no longer "holds" the document, he has a separate copy of it. And there is no way, just by looking at the document, to know who might have made a copy of it at some point in history.
Another way of putting it is this pseudocode:
File file = Open("image.png");
Image img = ImageFromFile(file);
file.Close();
...
img.Save("image.png");
Here no file is being opened at all, there's just a copy in RAM of its content.
Note: I actually checked that for Paint - Process Explorer can show you opened handles, I opened a file in Paint and there was no handle at all listed for a file of that name.
Here's what I came up with. I check all open processes for a window title. If the process has a window title, I see if it contains the name of the file I'm looking for.
It won't work 100% of the time since some applications can have multiple files open in a single instance.
I adapted it from this question:Getting a list of all applications
bool isFileOpen(string file)
{
string windowTitle = "";
Process[] myProcesses = Process.GetProcesses();
foreach (Process P in myProcesses)
{
if (P.MainWindowTitle.Length > 1)
{
windowTitle = P.MainWindowTitle;
if (windowTitle.Contains(file) == true)
{
return true;
}
}
}
return false;
}
I am trying to close an excel file named TestReport.xlsx using the below code. It is working when only one excel process is running but when I have multiple excel windows open, the MainWindowTitle changes and the code is not killing the desired excel process.
Process[] plist = Process.GetProcessesByName("Excel",".");
foreach(Process p in plist)
{
if (p.MainWindowTitle.Contains("TestReport.xlsx") && p.ProcessName == "EXCEL")
{
p.Kill();
}
}
The explanation you are giving in your question is not accurate enough. You must distinguish two cases:
Case 1: You have more than one Excel process each with one workbook. That means that e.g. all your excel workbooks each run in an own hosting excel window. You achieve this when e.g right-clicking on the excel icon, getting an empty excel workbook and loading the workbook you want into that process. In this case your approach works, as every excel process has its own title naming the workbook file name. The requested process is killed when iterating through the excel processes.
Case 2, which is the more "normal" case when working with excel, and to which you probably refer to: You have one excel process hosting more than one workbook, each possibly with several worksheets. In that case Excel acts as you describe and changes its window title (Multiple document interface).
In case 2, when there is only one single excel process present, you can close the workbook in the following way, using COM Interop:
private void button1_Click(object sender, EventArgs e)
{
CloseExcelWorkbook("TestReport.xlsx");
}
//put the following abbreviation to the "using" block: using Excel = Microsoft.Office.Interop.Excel;
internal void CloseExcelWorkbook(string workbookName)
{
try
{
Process[] plist = Process.GetProcessesByName("Excel", ".");
if (plist.Length > 1)
throw new Exception("More than one Excel process running.");
else if (plist.Length == 0)
throw new Exception("No Excel process running.");
Object obj = Marshal.GetActiveObject("Excel.Application");
Excel.Application excelAppl = (Excel.Application)obj;
Excel.Workbooks workbooks = excelAppl.Workbooks;
foreach (Excel.Workbook wkbk in workbooks )
{
if (wkbk.Name == workbookName)
wkbk.Close();
}
//dispose
//workbooks.Close(); //this would close all workbooks
GC.Collect();
GC.WaitForPendingFinalizers();
if (workbooks != null)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(workbooks);
//excelAppl.Quit(); //would close the excel application
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(excelAppl);
GC.Collect();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
This approach works in case you have only ONE Excel process running. When there are more than one present, the situation is more complicated as you must get access to all Excel processes. For a discussion for that case, see here.
Another point to observe is to release excel objects properly to avoid stale excel objects. See there.
If you omit the "dispose" activities, it may happen that on closing Excel and continuing to run your app the Excel process runs silently on, as an inspection in Task Manager shows.
We have a VSTO addin for Excel. The main functionality creates reports that are used to generate workbooks. When I run a batch of reports, I get a System.AccessViolationException when using Excel.Worksheet.Copy, which also crashes Excel. Here's how I recreate it:
1) Open and run report #1 with a single parameter which creates one workbook. We close the workbook.
2) Open and run the same report with several parameters. This create 5 workbooks but crashes when creating the second, but ONLY if we have run the first single output report (see step 1). If we remove the report from step 1 from the batch, this creates all 5 workbooks without error.
I've checked to make sure that the sheet we are copying is from the workbook is open, and is not referencing the first report. In fact, we close the first one so I know that it's not. Again, this ONLY happens if we have the report in step one, which it does not access at all, so how could that be affecting a sheet from a completely different workbook?
This doesn't even finish out my try/catch so that I can get more info. It simply blows up Excel and I have to restart.
UPDATE:
Here's the basic code:
function void ReplaceSheets(Dictionary<Excel.Worksheet, IReportSheet> sheetReports)
{
List<string> oldNames = new List<string>(sheetReports.Count);
foreach (Excel.Worksheet oldSheet in sheetReports.Keys)
{
Excel.Worksheet veryHiddenSheet = null;
Excel.Worksheet newSheet = null;
try
{
string sheetName = oldSheet.Name;
veryHiddenSheet = WorkbookHelper.FindSheet(this.DocumentView, MakeHiddenSheetName(sheetName, "--VH--"));
veryHiddenSheet.Visible = Excel.XlSheetVisibility.xlSheetVisible; //Sheet has to be visible to get the copy to work correctly.
veryHiddenSheet.Copy(this.DocumentView.Sheets[1], Type.Missing);//This is where it crashes
newSheet = (Excel.Worksheet)this.DocumentView.Sheets[1]; //Get Copied sheet
/* do other stuff here*/
}
finally
{
veryHiddenSheet = null;
newSheet = null;
}
}
}
I never found a way in VSTO to "fix" this. I switched code to NetOffice, and I was able to get some better error message. Excel/Com was not releasing the memory attached to the spreadsheets. I rebuilt the reports from blank 2010 spreadsheets and it took care of it. I think it was a corrupted 2007 spreadsheet that may have occured on converting to 2010 or something like that. I recommend NetOffice over VSTO because the exception handling is far superior, and you have access to the source code, but it does have it's quirks. (You'll need to pay attention to loading order for taskpanes.)
In my C# application, I can Open a new Excel process by clicking a button.
Then, it waits for input idle and load a new Excel file. I have to accomplish it because I want all macros and tools to be loaded before loading my document (otherwise, there is a rendering bug).
The Process Id is saved in a var, and when I click again on that button, with the PID, if the process already exists, the Process has the focus, otherwise, a new process is created :
Process processExcel = null;
if (pId_m.HasValue)
{
try
{
processExcel = Process.GetProcessById(pId_m.Value);
}
catch (Exception ex)
{
Log.MonitoringLogger.Error("Unable to find Pid : " + pId_m,ex);
}
}
if (processExcel != null && (processExcel.HasExited == false))
{
AutomationElement element = AutomationElement.FromHandle(processExcel.MainWindowHandle);
if (element != null)
{
element.SetFocus();
}
}
else
{
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "Excel.exe";
info.Arguments = " /e ";
processExcel_l = Process.Start(info);
pId_m = processExcel_l.Id;
processExcel_l.WaitForInputIdle();
processExcel_l.StartInfo = new ProcessStartInfo(path);
processExcel_l.Start();
}
The problem is the following : when I close the Excel document window (and not Excel window), and I click the button, the focus is set to the Excel process, but without any document...
This behavior is logic, but not working for what I want...
I have seen a software that load a new process and a new document inside, but when clicking on the document close button, the entire process was exited...
How to reproduce the same?
Edit : Ok,
Finally instead of setting the focus on the process, I launched a file on this process (which set focus if the file is already open).
It's not what I really wanted to do, but it solve my problem...
I would suggest utilizing the Excel COM model rather than running the process by hand. You can subscribe to events of the Worksheet and close the application.
These MSDN documents might be helpful:
http://msdn.microsoft.com/en-us/library/wss56bz7(v=vs.80).aspx
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.worksheet_members.aspx
Wait for Excel process ending and then close your app:
processExcel_l.WaitForExit();
Application.Exit();
To handle closing only Excel document (not Excel process) you probably need to reference to Excel API.