In our C# WinForms application, we generate PDF files and launch Adobe Reader (or whatever the default system .pdf handler is) via the Process class. Since our PDF files can be large (approx 200K), we handle the Exited event to then clean up the temp file afterwards.
The system works as required when a file is opened and then closed again. However, when a second file is opened (before closing Adobe Reader) the second process immediately exits (since Reader is now using it's MDI powers) and in our Exited handler our File.Delete call should fail because it's locked by the now joined Adobe process. However, in Reader we instead get:
There was an error opening this document. This file cannot be found.
The unusual thing is that if I put a debugger breakpoint before the file deletion and allow it to attempt (and fail) the deletion, then the system behaves as expected!
I'm positive that the file exists and fairly positive that all handles/file streams to the file are closed before starting the process.
We are launching with the following code:
// Open the file for viewing/printing (if the default program supports it)
var pdfProcess = new Process();
pdfProcess.StartInfo.FileName = tempFileName;
if (pdfProcess.StartInfo.Verbs.Contains("open", StringComparer.InvariantCultureIgnoreCase))
{
var verb = pdfProcess.StartInfo.Verbs.First(v => v.Equals("open", StringComparison.InvariantCultureIgnoreCase));
pdfProcess.StartInfo.Verb = verb;
}
pdfProcess.StartInfo.Arguments = "/N"; // Specifies a new window will be used! (But not definitely...)
pdfProcess.SynchronizingObject = this;
pdfProcess.EnableRaisingEvents = true;
pdfProcess.Exited += new EventHandler(pdfProcess_Exited);
_pdfProcessDictionary.Add(pdfProcess, tempFileName);
pdfProcess.Start();
Note: We are using the _pdfProcessDictionary to store references to the Process objects so that they stay in scope so that Exited event can successfully be raised.
Our cleanup/exited event is:
void pdfProcess_Exited(object sender, EventArgs e)
{
Debug.Assert(!InvokeRequired);
var p = sender as Process;
try
{
if (_pdfProcessDictionary.ContainsKey(p))
{
var tempFileName = _pdfProcessDictionary[p];
if (File.Exists(tempFileName)) // How else can I check if I can delete it!!??
{
// NOTE: Will fail if the Adobe Reader application instance has been re-used!
File.Delete(tempFileName);
_pdfProcessDictionary.Remove(p);
}
CleanOtherFiles(); // This function will clean up files for any other previously exited processes in our dictionary
}
}
catch (IOException ex)
{
// Just swallow it up, we will deal with trying to delete it at another point
}
}
Possible solutions:
Detect that the file is still open in another process
Detect that the second process hasn't really been fully exited and that the file is opened in the first process instead
I just dealt with this a couple of days ago.
When there is no instance open already, the document opens in a new instance directly.
When there is an instance already open, I believe that instance spawns a new instance which you don't actually get a handle to. What happens is control returns to your function immediately, which then goes and deletes the file before the new instance has had a chance to read the file -- hence it appears to not be there.
I "solved" this by not deleting the files immediately, but keeping track of the paths in a list, and then nuking all of them when the program exits (wrap each delete in a try/catch with an empty catch block in case the file has disappeared in the meantime).
I would suggest following approach:
Create files in user's temp directory (Path.GetTempPath). You can create some sub-folder under it.
Attempt to delete files only when last instance of process gets exited (i.e. you need to count number of processes that you had launched, on exit, decrement the count and when it becomes zero, attempt to delete (all) files that are open so far)
Try to clean-up created sub-folder (under temp directory) while starting and ending the application. You can even attempt for periodic clean-up using timer.
Related
I'm trying to delete .xlsx or .pdf files after using them. When files are created I display them, but then users want automatic file deletion after closing them.
I've tried couple of things, but none of them seem to work properly. Issue:
When opened multiple files (.xlsx or .pdf) I can't terminate a single process, like just a single file. Instead what happens is that file get's deleted only when I close all same processes (Excel or PDF files). As I investigated this happens because Excel or PDF works as one instance only. However code works as expected when I have only one file opened...
This is what I have so far:
var process= Process.Start(file_path); //file_path is global variable
Set_event(process);
private void Set_event(Process process)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(Delete_File);
}
public void Delete_File(object sender, EventArgs e)
{
//Delete file on close
File.Delete(file_path);
}
I've also tried with DeleteOnClose method of FileOptions, but unfortunally that doesn't display file to user and doesn't quite delete file immediately after using them, only after my win app is closed. That isn't my desired output, but at least files are deleted, so If I could fix that I would be partially satisfied too. Here is my line for that:
var open_file = new FileStream(file_path,FileMode.Open, FileAccess.ReadWrite,FileShare.ReadWrite, 512, FileOptions.DeleteOnClose);
With all that said, are there any other options I missed ? Thanks for help in advance.
I've tried almost everything I could find (different variations of Exited_Event for Process, monitoring with FileSystemWatcher, creating files with DeleteOnClose - even API), but none of them worked as expected.
Everything ends or fails with issue I described in first place - some apps, like Microsoft Excel or Adobe Acrobat uses one instance to open a file (.pdf or .xls/.xlsx), so you can't just reference a single file as object while you have opened more files. That means you either end up with an error when trying to assign Exited_event to single file, or no error but file gets deleted only when you close all files with same type...
BUT fortunate enough I figured out one thing: WHEN you have opened more than one file in question (.pdf or .xlsx) something happens in background of OS: If you loop through processes of same type at that time, you'll get a list of particular instance that is in use.
In other words, while you have 2 Excel files opened, loop through processes is showing you only a file which is currently active for "EXCEL" process.
So, that leaded me to a completely new approach that might solve this issue. In order to have a complete solution for this you have to:
1. Create a method to check whether file is no longer in use.
2. Set a Timer with a delay of 2 seconds, to make sure process really ends. Maybe this should be incremented for different purposes...
3. Set a Timer_tick event, where you loop processes to see whether particular file is listed as active, and If user has already closed this file. As described by other users this method isn't quite accurate, but with setting delay for Timer I think there shouldn't be any problems anymore.
Here is a complete code for this (for .pdf and .xlsx - that is what I needed):
//as global variable
System.Windows.Forms.Timer delete_file = new System.Windows.Forms.Timer();
Process.Start(file_path); //file_path is global variable
delete_file.Tick += new EventHandler(timer_Tick);
delete_file.Interval = (2000);
delete_file.Enabled = true;
delete_file.Start();
private void timer_Tick(object sender, EventArgs e)
{
Boolean file_is_opened = false;
// Loop processes and list active files in use
foreach (var process in Process.GetProcesses())
{
if (process.MainWindowTitle.Contains(Path.GetFileName(file_path)))
{
file_is_opened = true;
}
}
//If our file is not listed under active processes we check
//whether user has already closed file - If so, we finally delete It
if (file_is_opened==false)
{
if (!File_In_Use(new FileInfo(file_path)))
{
File.Delete(file_path);
delete_file.Enabled = false;
delete_file.Stop();
return;
}
}
}
private bool File_In_Use(FileInfo file)
{
//Method to check whether file is in use
FileStream stream = null;
try
{
//If file doesn't exist
if (!file.Exists)
{
return false;
}
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//File is unavailable:
//because someone writes to It, or It's being processed
return true;
}
finally
{
if (stream!=null)
{
stream.Close();
}
}
//File not locked
return false;
}
This is how I did It. It might not be a perfect solution, but that works for me on Win 10 with no errors so far.
If someone has a suggestion to fix upper code, please let me know. Otherwise I hope this will help someone in future as I noticed there were already some questions about this in past, with no proper answer.
I am working on an application in C# which does the following things:
Write an EXE to disk
Execute the EXE through Process.Start()
I am now trying to ensure that the EXE will be deleted once it is closed.
The easiest way to do so is to set the FileOptions.DeleteOnClose parameter when creating the EXE using File.Create.
However, this means that the EXE can not be executed while is in use. Once the file handle is closed, the EXE is immediately deleted before it can be executed.
Is there any way to retain a "weak reference" to the EXE in my application which does not lock the file and allows it to be executed? Alternatively, is there any way to unlock the EXE for execution with the file handle still open? Are there any other obvious solutions I am missing?
CLARIFICATION A: I am aware of other methods to delete files in use which will delete the file eventually (e.g. upon reboot). I am however looking for a method to delete the file immediately once it starts executing which is handled by the OS (e.g. when running a batch that first executes the file and then deletes it, the file would remain on disk if the batch job is terminated).
CLARIFICATION B: To explain the bigger picture: The application receives and decrypts an executable file. After decryption, the file should be executed. However, I want to make sure the decrypted version of the EXE does not stay on disk. Ideally, I also want to prevent users from copying the decrypted EXE. However, since the decryption application runs as the same user, this will be impossible to achieve in a truly secure fashion as both have the same privileges on the system.
You could use Process.WaitForExit:
var process = Process.Start(processPath);
process.WaitForExit();
// File.Delete(processPath); // Not strong enough (thanks to Binary Worrier)
DeleteOrDie(processPath); // Will attempts anything to delete the file.
But it gives the possibility to copy the exe from where you writed it.
A good solution is to run it from memory.
If your target exe is a CLR program, you can use the Assembly.Load function:
// read the file and put data in bin
...
Assembly a = Assembly.Load(bin);
MethodInfo method = a.EntryPoint;
if (method == null) throw new NoEntryPointException();
object o = a.CreateInstance(method.Name);
method.Invoke(o, null);
More details here.
If you want to load/execute any exe in memory, you could use the Nebbett’s Shuttle approach but you will need to code it in C/C++ and make a call to it from C#.
Also it looks like Microsoft doesn't like it (security issues) and I don't think you can achieve it from C# only. Good anti-virus will probably detect it.
In a not very good way but a way that can give you what you want, I suggest this solution:
(I use a Console Application with some input arguments for this solution)
[1: ] Write a function to check opened processes:
/// <summary>
/// Check application is running by the name of its process ("processName").
/// </summary>
static bool IsProcessOpen(string processName)
{
foreach (Processing.Process clsProcess in Processing.Process.GetProcesses())
{
if (clsProcess.ProcessName.ToUpper().Contains(processName.ToUpper()))
return true;
}
return false;
}
[2: ] Define some variables:
static bool IamRunning = true;
static int checkDuration = 1; // in seconds
[3: ] Use a Thread for run a loop for checking:
Thread t = new Thread(delegate() {
DateTime lastCheck = DateTime.MinValue;
while (IamRunning)
{
var now = DateTime.Now;
int dd = (now.Hour - lastCheck.Hour) * 3600 + (now.Minute - lastCheck.Minute) * 60 + now.Second - lastCheck.Second;
if (dd >= checkDuration)
if (!IsProcessOpen("ProcessName"))
{
delApplication(); // You have a function to delete ...
break;
}
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
[4: ] Use a loop at the end of the program:
while (t.ThreadState == ThreadState.Running)
{
// just wait.
}
Note: This solution by Console Application in my low computer have 50% usage of CPU.
My application is System Tray Application, using C#, .NET 4.0.
I'm trying to display many PDF files at a time and each PDF should split screen with other window, that I determine by ProcessName, that's all.
The difficulty is that I need to wait until user closes that window (.WaitForExit() method), because later I'm deleting PDF file. And here problem comes.
In first thread everything goes fine but the problem is when i try to show second PDF file window processList[0] THOUGH MoveWindow function returns true, and handle to that window is also correct only pdf window is resizing, the other window fails.
In main method the only thread that I'm creating (I call this piece of code couple of times, everytime user want to see pdf file):
Thread pdfThread = new Thread(() => ShowPdfFile(fullPath));
pdfThread.Start();
Then (simplified code)
public static void ShowPdfFile(string fileName)
{
try
{
Process pdfProcess = Process.Start(fileName);
Thread.Sleep(500);
string windowTitle = GetActiveWindowTitle();
IntPtr pdfHandle = (IntPtr)FindWindow(null, windowTitle);
MoveWindow(pdfHandle, 0, 0, 0, 0, true);
Process[] processList = Process.GetProcessesByName("someProcess");
MoveWindow(processList[0].MainWindowHandle, 0, 0, 0, 0, true);
pdfProcess.WaitForExit();
MoveWindow(processList[0].MainWindowHandle, 0, 0, max, max, true);
}
catch (Exception ex)
{
LogToFile(ex);
}
finally
{
try
{
File.Delete(fileName);
}
catch
{
LogToFile("Cannot delete file");
}
}
UPDATE: Well, I was debugging it whole day but just now i note that on second thread it doesn't wait on line pdfProcess.WaitForExit();
What should I change to force thread to wait for that exact pdfProcess exit?
There is a basic problem to your usage:
You start the external PDF application for each file. However this doesn't ensure that you have more than one process.
For example Acrobat reader only starts a single process. Additional files will just be "added" as new windows to the first process (You can check this by trying to manually open Acrobat reader twice --> won't work).
So in short: If you cannot control which PDF reader is used (and can ensure that you have a single process for EACH file) your approach will not work.
Note: Theoretically it would be possible to wait until the user closes the "reader window" that contains the specific file. However I strongly advise against this:
Looking up a window in a different process is very error prone (unless the process is explicitly designed in such a way...)
The solution would again depend on the reader application (you cannot be sure that Acrobat and Nitro use similar architecture to just name two readers)
Principally, if this feature is very important, you should consider to buy a PDF viewer component that allows to show the PDF's as windows of your own process.
Edit
The reason that the second thread isn't waiting is that the functionality to "add" a file to the first process uses a temporary process:
Temporary process is started for new file
Temporary process checks if the application is already running
Temporary process notifies the first process to open the specific file
Temporary process shuts down.
So waiting for that process will return almost immediately since the process in question already has stopped (or will stop in just a couple of milliseconds).
Daniel is right, however I found a solution! Here, maybe someone will use it.
Small comment: We must use winapi solutions instead of .WaitForExit().
The most important part is while loop, that waits for close pdf window.
Remember that FindWindow() and IsWindow() methods are from user32.dll (winapi).
This code run process based on it's paths, then gets it's handle and wait for it's closure.
Process pdfProcess = new Process();
pdfProcess.StartInfo.FileName = filePath;
if (pdfProcess.Start())
{
Thread.Sleep(500);
Process[] processlist = Process.GetProcesses();
string windowTitle = string.Empty;
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle) && process.MainWindowTitle.Contains(fileName))
{
windowTitle = process.MainWindowTitle;
}
}
IntPtr pdfHandle = FindWindow(null, windowTitle);
while (IsWindow(pdfHandle) && userExitedApp == false)
Thread.Sleep(100);
}
Multiple threads could utilize one method one after the other. Perhaps this video will help
https://www.youtube.com/watch?v=XhGXh9Z5GTw&feature=em-upload_owner
I have a process A that reads in some data produced by some other process B. The data is 'exchanged' via the file system. To ensure that the file exists, process A currently checks for the file's existence like this:
while (!File.Exists(FileLocation))
{
Thread.Sleep(100);
}
This only seems to work 99 percent of the time. The other 1 percent of the time, process A establishes that the file exists but process B has not written everything yet (i.e. some data is missing).
Is there another simpler way to make the above situation more bullet proofed? Thanks.
Is there another simpler way to make the above situation more bullet proofed?
You could use a Mutex for reliable inter-process synchronization. Another possibility is to use a FileSystemWatcher.
After determining that the file exists, you can try opening the file for exclusive access, which will fail if another process still has the file open:
try
{
File.Open("foo",FileMode.Open,FileAccess.Read,FileShare.None);
}
catch(IOException ex)
{
// go back to
}
Given that you say that you can change both processes' code, you can use an EventWaitHandle to communicate between the processes.
In your program that creates the file, in the Main() method you can create an EventWaitHandle and keep it around until the end of the program. You'll need to pass the EventWaitHandle object around in your program so that it is available to the bit of code that creates the file (or provide some method that the file-creating code can call to set the event).
using (EventWaitHandle readySignaller = new EventWaitHandle(false, EventResetMode.ManualReset, "MySignalName"))
{
// Rest of program goes here...
// When your program creates the file, do this:
readySignaller.Set();
}
Then have some code like this in the program that's waiting for the file:
// Returns true if the wait was successful.
// Once this has returned true, it will return false until the file is created again.
public static bool WaitForFileToBeCreated(int timeoutMilliseconds) // Pass Timeout.Infinite to wait infinitely.
{
using (EventWaitHandle readySignaller = new EventWaitHandle(false, EventResetMode.ManualReset, "MySignalName"))
{
bool result = readySignaller.WaitOne(timeoutMilliseconds);
if (result)
{
readySignaller.Reset();
}
return result;
}
}
NOTE: If we successfully wait note that I am resetting the signal and it will remain reset until the other process sets it again. You can handle the logic differently if you need to; this is just an example.
Essentially what we are (logically) doing here is sharing a bool between two processes. You have to be careful about the order in which you set and reset that shared bool.
Try the FileSystemWatcher.
Listens to the file system change notifications and raises events when
a directory, or file in a directory, changes.
I have an application that uses MSWord automation to edit some documents, after they save and close word I need to grab the modified file and put it back on the repository, there is only one scenario where I can't get it to work and that is
when the user makes changes to the file, selects to close word and selects yes to save the file
there are 2 events that I'm using:
DocumentBeforeSave
Quit
on the Quit event I'm trying to load the .docx file from disk but on this particular scenario I get an IOException because the file is still in use, somehow I need to wait until after the Quit event has been processed, which is when Word is actually closed and the file is no longer being used
right now I have it working using this
word.Visible = true;
while (!wordDone) { //gets changed to true on the Quit event
System.Threading.Thread.Sleep(100);
}
bool error = false;
do {
try { //need to load the contents of the modified file
ls.Content = System.IO.File.ReadAllBytes(provider.GetFileName());
error = false;
}
catch (System.IO.IOException) {
error = true;
System.Threading.Thread.Sleep(200);
}
} while (error);
while this works it is very ugly, I need a way to fire an event after the Quit event has been handled, or block the current thread while word is still running, or get an event after the document has been saved, the bottom line is I need a clean way to load the file after it has been saved and word is closed. DocumentAfterSave would be awesome, but doesn't seem to exist.
I Also tried unhooking the Quit handler and calling word.Quit on the Quit handler, that made no difference
I'm also investigating the use of ManualResetEvent or related classes, so far it almost works, but I still need to pause after it has been signaled to make sure word is closed and the file is no longer in use
I faced similar problem in the past as well. I dont think there is any nice clean way but instead of doing it like your above, how about considering this (will suit if you have a controlled environment)
Create word app
Get the Process ID immediately by using GetProcesses matching Winword and the last one in the list return should be the one you are after. This is not 100% reliable in multiuser environment.
After word quit, use the Thread.Sleep loop to ensure the PID no longer exist.
Reading the docx for your custom operations
I used to have the same problem. Using ReleaseComObject on all COM-related objects did the trick (that is, on your Word document object and your Word.Application object). That way you ensure that all dirty locks are removed after the COM object has been destroyed. Close the document and application with the Interop API. I use:
var localWordapp = new Word.Application();
localWordapp.Visible = false;
Word.Document doc = null;
// ...
if (doc != null)
{
doc.Close();
System.Runtime.InteropServices.Marshal.ReleaseComObject(doc);
}
localWordapp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(localWordapp);