In my program I try to start a new process (open video file in a default player). This part works OK. Later when I try to close the process (player) I get an error:
System.InvalidOperationException: No process is associated with this object.
My code:
string filename = "747225775.mp4";
var myProc = new Process()
{
StartInfo = new ProcessStartInfo(filename)
};
myProc.Start();
Thread.Sleep(5000);
try
{
myProc.Kill(); //Error is here
}
catch (Exception ex)
{
Debug.WriteLine(ex);
Debugger.Break();
}
What is wrong?
Process.Start associates the Process object with native process handle only when it spawns new process directly. When filename is used as an argument instead of executable name, Process searches registry for association settings via shell32.dll functions and the exact behavior depends on them.
When association is configured in traditional way, to call command line and transfer file name as 1st argument (such as for Notepad), Process.Start spawns new process directly and correctly associates object with native handle. However, when association is configured to execute COM-object (such as for Windows Media Player), Process.Start only creates some RPC query to execute COM object method and returns without associating object with process handle. (The actual process spawn occurs in svchost.exe context, according to my tests)
This issue can be solved by following modified process start method:
using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
namespace ProcessTest
{
public partial class Form1 : Form
{
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, ref uint pcchOut);
/*Modified Process.Start*/
public static Process TrueProcessStart(string filename)
{
ProcessStartInfo psi;
string ext = System.IO.Path.GetExtension(filename);//get extension
var sb = new StringBuilder(500);//buffer for exe file path
uint size = 500;//buffer size
/*Get associated app*/
uint res = AssocQueryString(AssocF.None, AssocStr.Executable, ext,null, sb, ref size);
if (res != 0)
{
Debug.WriteLine("AssocQueryString returned error: " + res.ToString("X"));
psi = new ProcessStartInfo(filename);//can't get app, use standard method
}
else
{
psi = new ProcessStartInfo(sb.ToString(), filename);
}
return Process.Start(psi);//actually start process
}
public Form1()
{
InitializeComponent();
}
private void button2_Click(object sender, EventArgs e)
{
string filename = "c:\\images\\clip.wmv";
var myProc = TrueProcessStart(filename);
if (myProc == null)
{
MessageBox.Show("Process can't be killed");
return;
}
Thread.Sleep(5000);
try
{
myProc.Kill();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
[Flags]
enum AssocF : uint
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_FixedProgId = 0x800,
IsProtocol = 0x1000,
InitForFile = 0x2000,
}
enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
SupportedUriProtocols,
Max,
}
}
Here we get the file type's associated application via AssocQueryString. The returned value is then passed to ProcessStartInfo. However it does not always work, so we sometimes have to resort to standart method. For example, image files does not have any associated exe, it's just dll being loaded into explorer's process. So we can't outright kill process in this case.
to answer your question: "What is wrong?"
I can say the underline cause of this is related to Windows Apps that are launched to handle the type of file (.mp4).
From what I can determine.. there isn't anything wrong with your code sample except that it doesn't account for this scenario (in which, admittingly, I do not understand why it behaves this way).
To replicate this, I used your code sample and a image file (.png). the program launches with 'Photos' by default.
I changed .png files to launch with Paint application by default, then ran the program again. The code sample you've provided worked fine on the desktop application.
Related
Is there a way to call this dialog from c#?
I traced the apis, but non of the calls seems to call the dialog. Dsuiext.dll sounds very promissing, but there I foud just a LDAP browser.
This Microsoft sample provides the expected result. You pass an ADS path as parameter and it calls the property window.
PropSheetHost.exe "LDAP://CN=user,DC=MyDomain,DC=MyTldDomain"
It is important that it is case sensitive, so "ldap://.." doesn't work. The code is definitely not designed to get called multiple times before terminating, so it is probably the best way to use the exe without changes like that:
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = #"PropSheetHost.exe";
startInfo.Arguments = #"LDAP://CN=user,DC=MyDomain,DC=MyTldDomain";
Process.Start(startInfo);
I wrote a wrapper to call it directly from C# and corrected the error what I found. Since I haven't programmed C for nearly 30 years, I am grateful for any hint if the implementation is incorrect. All changes are explained and marked with //MW: .... This works in my code, but you can open only one windows at a time and need to close it before opening another window.
The entry point:
__declspec(dllexport) HRESULT __stdcall CallPropSheetHost(const char* ldapPath)
{
TCHAR szTemp[MAX_ADSPATH_CHARS];
LPWSTR pwszADsPath = NULL;
HRESULT hr = E_FAIL; // MW: move before "if" and preset
CoInitialize(NULL);
{
//MW: copy the parameter
_tcsncpy_s(szTemp, ARRAYSIZE(szTemp), ldapPath, MAX_ADSPATH_CHARS - 1);
}
DWORD dwChars = lstrlen(szTemp) + 1;
pwszADsPath = new WCHAR[dwChars];
if (pwszADsPath)
{
HINSTANCE hInstance = NULL;
HWND hwndConsole = GetConsoleWindow();
if (hwndConsole)
{
hInstance = (HINSTANCE)(LONG_PTR)GetWindowLongPtr(hwndConsole, GWLP_HINSTANCE);
}
CPropSheetHost* pHost = new CPropSheetHost(hInstance);
LocalToWideChar(pwszADsPath, dwChars, szTemp, dwChars);
// Hold a reference count for the CPropSheetHost object.
pHost->AddRef();
hr = pHost->SetObject(pwszADsPath);
if (FAILED(hr))
{
goto ExitMain;
}
//MW: My implmentation returns E_Fail when the registration fails
hr = pHost->Run();
if (FAILED(hr))
{
pHost->Release();
goto ExitMain;
}
//Release the CPropSheetHost object. Other components may still hold a
//reference to the object, so this cannot just be deleted here. Let
//the object delete itself when all references are released.
pHost->Release();
}
ExitMain:
if (pwszADsPath)
{
delete pwszADsPath;
return hr; //MW: return th HRESULT
}
CoUninitialize();
return hr; //MW: return th HRESULT
}
The original implementation doesn't unregister a class. Therefore it fails when it's used multiple times. These are my changes in PropSheetHost.cpp to fix that.
//MW: new method
void CPropSheetHost::_UnregisterWndClass()
{
UnregisterClass(m_szHiddenWindowClass, m_hInst);
}
//MW: added a HRESULT and calling of "_UnregisterWndClass"
HRESULT CPropSheetHost::Run()
{
if (!m_spADObject.p)
{
return E_FAIL; //MW: added a return value
}
// Create the hidden window.
m_hwndHidden = _CreateHiddenWindow();
if (!m_hwndHidden)
{
return E_FAIL; //MW: added a return value
}
/*
Display the proeprty sheet. This is a modal call and will not return
until the property sheet is dimissed.
*/
_CreatePropertySheet();
// Destroy the hidden window.
DestroyWindow(m_hwndHidden);
//WM: Unregister the class; this call was missing
_UnregisterWndClass();
return ERROR_SUCCESS; //MW: added a return value
}
... and the call from C#:
using System;
using System.Runtime.InteropServices;
using System.Windows;
const int MAX_ADSPATH_CHARS = 2047;
[DllImport("PropSheetHost.dll", EntryPoint = "CallPropSheetHost", CallingConvention = CallingConvention.Cdecl)]
private static extern int CallPropSheetHost(string ldapPath);
///CAUTION:
/// * This call is modal and won't return until the called window is closed
/// * You can open only one window at a time. Trying opening a second window before closing the the first one fails
public static int Win32PropSheetHost(string distinguishedName, string serverOrDomain = null)
{
if (string.IsNullOrEmpty(distinguishedName)) throw new ArgumentNullException("EXC262: the distinguished name must not be null nor empty");
//<----------
/// Caution: "LDAP" must be uppercase!
string ldapPath = string.IsNullOrEmpty(serverOrDomain)
? $"LDAP://{ distinguishedName }"
: $"LDAP://{ serverOrDomain }/{ distinguishedName }";
if (ldapPath.Length > MAX_ADSPATH_CHARS) throw new ArgumentException($"EXC263: the complete lds path must not be longer than { MAX_ADSPATH_CHARS } characters (current path: \"{ ldapPath }\")");
//<----------
try
{
return CallPropSheetHost(ldapPath);
}
catch (DllNotFoundException ex)
{
/// Could't find a dll, mos likely our propsheethost.dll
return ResultWin32.ERROR_DLL_NOT_FOUND;
}
}
For the translation of the Windows Error Codes I use this class.
Currently I'm porting a c++ exe launch to c#. I'm able to read through and understand the c++ code, but I'm struggling to find the c# equivalent. I believe that the original code launches the exe by utilizing the command prompt.
I think it would be best to display the code that I am porting, so here it is:
// This is basically running an exe to compile a file that I create
short rtncod;
int GPDataAge = FileAge(SelectedPath + GPDATA); //Checks age of GPDATA if it exists
STARTUPINFO si; // Startup information structure
PROCESS_INFORMATION pi; // Process information structure
memset(&si, 0, sizeof(STARTUPINFO)); // Initializes STARTUPINFO to 0
si.cb = sizeof(STARTUPINFO); // Set the size of STARTUPINFO struct
AnsiString CmdLine = Config->ReadString("Configuration","CRTVSM","CRTVSM.EXE . -a"); // Windows version requires path
rtncod = (short)CreateProcess(
NULL, // pointer to name of executable module
CmdLine.c_str(), // pointer to command line string
NULL, // pointer to process security attributes
NULL, // pointer to thread security attributes
FALSE, // handle inheritance flag
CREATE_NEW_CONSOLE, // creation flags
NULL, // pointer to new environment block
NULL, // pointer to current directory name
&si, // pointer to STARTUPINFO
&pi); // pointer to PROCESS_INFORMATION
if (!rtncod) /*If rtncod was returned successful**/ {
int LastError = GetLastError();
if (LastError == 87 /* Lookup 87 error **/ && AnsiString(SelectedPath + GPDATA).Length() > 99)
ShowMessage("CRTASM could not run due to extremely long path name. Please map or move the folder to shorten the path");
else
ShowMessage("Could not compile VSMInfo.dat =>" + IntToStr(LastError));
}
else /* If successful **/ {
unsigned long ExitCode;
// CartTools will 'lock up' while waiting for CRTASM
do {
rtncod = GetExitCodeProcess(pi.hProcess,&ExitCode);
} while (rtncod && ExitCode == STILL_ACTIVE);
if (rtncod == 0) {
rtncod = GetLastError();
ShowMessage("Could not watch CRTVSM compile VSMInfo.dat =>" + IntToStr(GetLastError()));
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
if (GPDataAge == FileAge(SelectedPath + GPDATA)) // date/time didn't change!
Application->MessageBox(AnsiString("Output blocking file (" + SelectedPath + GPDATA") failed to be updated. Check operation of CRTVSM.EXE before using "GPDATA" with SAM/CMS!").c_str(),"CRTVSM Error",MB_OK|MB_ICONHAND);
All of this may not be relevant, and you may not know where my personal elements come from, but that is okay as I am only concerned with the MICROSOFT process elements (such as CreateProcess and STARTUPINFO).
So far I have looked at the Process.Start method provided in this question, but do not think that it allows me to go through the same processes as the ones listed above.
My question is, what class or methods can I use to customize my exe launch in a equivalent manner to the launch that is performed in the c++ code above?
UPDATE: Currently, I have the executable file located inside a folder that I created in the solution of my program. To launch the executable I am using the ProcessStartInfo class.
//The folder that the exe is located in is called "Executables"
ProcessStartInfo startInfo = new ProcessStartInfo("Executables\\MYEXECUTABLE.EXE");
Process.Start(startInfo);
Whenever I run the above lines of code I get a Win32Exception was unhandled, and it says that "The system cannot find the file specified".
The C++ code isn't using a command 'prompt', per se, but launching a process by providing a path the the executable to CreateProcess. You can accomplish the same thing in C# with the Process class. Configure Process.StartInfo and call the Start method.
Regarding launching the executable with a specific path: if you don't specify a full path then you are at the mercy of the working directory. If the exe is the same directory as the running executable, or a subdirectory of it, then you can construct the path like this:
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"Executables\MYEXECUTABLE.EXE");
ProcessStartInfo startInfo = new ProcessStartInfo(path);
Process.Start(startInfo);
Adding on to jltrem, an example of Process.Start is:
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo(v=vs.110).aspx
using System;
using System.Diagnostics;
using System.ComponentModel;
namespace MyProcessSample
{
class MyProcess
{
// Opens the Internet Explorer application.
void OpenApplication(string myFavoritesPath)
{
// Start Internet Explorer. Defaults to the home page.
Process.Start("IExplore.exe");
// Display the contents of the favorites folder in the browser.
Process.Start(myFavoritesPath);
}
// Opens urls and .html documents using Internet Explorer.
void OpenWithArguments()
{
// url's are not considered documents. They can only be opened
// by passing them as arguments.
Process.Start("IExplore.exe", "www.northwindtraders.com");
// Start a Web page using a browser associated with .html and .asp files.
Process.Start("IExplore.exe", "C:\\myPath\\myFile.htm");
Process.Start("IExplore.exe", "C:\\myPath\\myFile.asp");
}
// Uses the ProcessStartInfo class to start new processes,
// both in a minimized mode.
void OpenWithStartInfo()
{
ProcessStartInfo startInfo = new ProcessStartInfo("IExplore.exe");
startInfo.WindowStyle = ProcessWindowStyle.Minimized;
Process.Start(startInfo);
startInfo.Arguments = "www.northwindtraders.com";
Process.Start(startInfo);
}
static void Main()
{
// Get the path that stores favorite links.
string myFavoritesPath =
Environment.GetFolderPath(Environment.SpecialFolder.Favorites);
MyProcess myProcess = new MyProcess();
myProcess.OpenApplication(myFavoritesPath);
myProcess.OpenWithArguments();
myProcess.OpenWithStartInfo();
}
}
}
I am in need of an example, that can let me pass a parameter
e.g. executing delete.exe /killme.txt
So it will use the "MoveFile" to delete killme.txt after reboot.
Although please not the MS precompiled version, as it has an annoying disclaimer, every time its run on a different computer.
You'll need the P/Invoke declarations for MoveFileEx:
[Flags]
internal enum MoveFileFlags
{
None = 0,
ReplaceExisting = 1,
CopyAllowed = 2,
DelayUntilReboot = 4,
WriteThrough = 8,
CreateHardlink = 16,
FailIfNotTrackable = 32,
}
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool MoveFileEx(
string lpExistingFileName,
string lpNewFileName,
MoveFileFlags dwFlags);
}
And some example code:
if (!NativeMethods.MoveFileEx("a.txt", null, MoveFileFlags.DelayUntilReboot))
{
Console.Error.WriteLine("Unable to schedule 'a.txt' for deletion");
}
Because you want to perform this after reboot as a requirement, you could use the Windows Task Scheduler API. You can invoke this in C# by adding a reference to the COM library TaskScheduler 1.1 Type Library. Below is a full code example on running Notepad.exe at logon.
Also, here is another resource: http://bartdesmet.net/blogs/bart/archive/2008/02/23/calling-the-task-scheduler-in-windows-vista-and-windows-server-2008-from-managed-code.aspx
You could call the system command DEL from Windows Command line, potentially with this code.
namespace TaskSchedulerExample {
using System;
using TaskScheduler;
class Program {
static void Main(string[] args) {
var scheduler = new TaskSchedulerClass();
scheduler.Connect(null, null, null, null);
ITaskDefinition task = scheduler.NewTask(0);
task.RegistrationInfo.Author = "DCOM Productions";
task.RegistrationInfo.Description = "Demo";
ILogonTrigger trigger = (ILogonTrigger)task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON);
trigger.Id = "Logon Demo";
IExecAction action = (IExecAction)task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC);
action.Id = "Delete";
action.Path = "c:\\delete.exe"; // Or similar path
action.WorkingDirectory = "c:\\"; // Working path
action.Arguments = "c:\\killme.txt"; // Path to your file
ITaskFolder root = scheduler.GetFolder("\\");
IRegisteredTask regTask = root.RegisterTaskDefinition("Demo", task, (int)_TASK_CREATION.TASK_CREATE_OR_UPDATE, null, null, _TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN, "");
//Force run task
//IRunningTask runTask = regTask.Run(null);
}
}
}
This gives you some flexibility. You could run your own delete.exe, or you could potentially invoke the Windows Command Line to execute the DEL command.
i have got a 2 exe (console)
first exe provides facility to convert video formats.
second exe provides facility to split video.
in my application i have got 2 buttons with which both process are working fine separately.
but now i wants to make it work on single click. means first it should convert video using first exe and then split that using second exe.
the problem is that how to find that first exe has finished its work so that i can start second exe to work on output's of first exe.
i am running both exe by creating process.
NOTE: my both exe gets close when they done their work, so may be we can check for existing of there process but i wants experts opinion for this.
Thanks
If you are using a GUI, it will halt if you use WaitForExit.
Here's an asynchronous example. You will have to adapt it to your needs:
using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Threading;
class ConverterClass
{
private Process myProcess = new Process();
private bool finishedFlag = false;
/* converts a video asynchronously */
public void ConvertVideo(string fileName)
{
try
{
/* start the process */
myProcess.StartInfo.FileName = "convert.exe"; /* change this */
/* if the convert.exe app accepts one argument containing
the video file, the line below does this */
myProcess.StartInfo.Arguments = fileName;
myProcess.StartInfo.CreateNoWindow = true;
myProcess.EnableRaisingEvents = true;
myProcess.Exited += new EventHandler(myProcess_Exited);
myProcess.Start();
}
catch (Exception ex)
{
/* handle exceptions here */
}
}
public bool finished()
{
return finishedFlag;
}
/* handle exited event (process closed) */
private void myProcess_Exited(object sender, System.EventArgs e)
{
finishedFlag = true;
}
public static void Main(string[] args)
{
ConverterClass converter = new ConverterClass();
converter.ConvertVideo("my_video.avi");
/* you should watch for when the finished method
returns true, and then act accordingly */
/* as we are in a console, the host application (we)
may finish before the guest application (convert.exe),
so we need to wait here */
while(!converter.finished()) {
/* wait */
Thread.Sleep(100);
}
/* video finished converting */
doActionsAfterConversion();
}
}
When the program exits, finishedFlag will be set to true, and the finished() method will start returning that. See Main for "how you should do it".
How about something like :
Process p1 = Process.Start("1.exe");
p1.WaitForExit();
Process p2 = Process.Start("2.exe");
if it is in windows just call WaitForSingleObject on the handle returned by CreateProcess
I am opening a file with read access and allowing subsequent read|write|delete file share access to the file (tailing the file). If the file is deleted during processing is there a way to detect that the file is pending delete (see Files section http://msdn.microsoft.com/en-us/library/aa363858(v=VS.85).aspx)? If some outside process (the owning process) has issued a delete, I want to close my handle as soon as possible to allow the file deletion so as not to interfere with any logic in the owning process.
I'm in C# and see no method of detecting the pending delete. The file was opened using a FileStream object. Is there some method for detecting the delete in C# or in some other windows function?
You can use the Windows API function GetFileInformationByHandleEx to detect a pending delete on a file you have open. The second argument is an enumeration value which lets you specify what kind of information the function should return. The FileStandardInfo (1) value will cause it to return the FILE_STANDARD_INFO structure, which includes a DeletePending boolean.
Here is a demonstration utility:
using System;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
internal static class Native
{
[DllImport("kernel32.dll", SetLastError = true)]
public extern static bool GetFileInformationByHandleEx(IntPtr hFile,
int FileInformationClass,
IntPtr lpFileInformation,
uint dwBufferSize);
public struct FILE_STANDARD_INFO
{
public long AllocationSize;
public long EndOfFile;
public uint NumberOfLinks;
public byte DeletePending;
public byte Directory;
}
public const int FileStandardInfo = 1;
}
internal static class Program
{
public static bool IsDeletePending(FileStream fs)
{
IntPtr buf = Marshal.AllocHGlobal(4096);
try
{
IntPtr handle = fs.SafeFileHandle.DangerousGetHandle();
if (!Native.GetFileInformationByHandleEx(handle,
Native.FileStandardInfo,
buf,
4096))
{
Exception ex = new Exception("GetFileInformationByHandleEx() failed");
ex.Data["error"] = Marshal.GetLastWin32Error();
throw ex;
}
else
{
Native.FILE_STANDARD_INFO info = Marshal.PtrToStructure<Native.FILE_STANDARD_INFO>(buf);
return info.DeletePending != 0;
}
}
finally
{
Marshal.FreeHGlobal(buf);
}
}
public static int Main(string[] args)
{
TimeSpan MAX_WAIT_TIME = TimeSpan.FromSeconds(10);
if (args.Length == 0)
{
args = new string[] { "deleteme.txt" };
}
for (int i = 0; i < args.Length; ++i)
{
string filename = args[i];
FileStream fs = null;
try
{
fs = File.Open(filename,
FileMode.CreateNew,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete);
byte[] buf = new byte[4096];
UTF8Encoding utf8 = new UTF8Encoding(false);
string text = "hello world!\r\n";
int written = utf8.GetBytes(text, 0, text.Length, buf, 0);
fs.Write(buf, 0, written);
fs.Flush();
Console.WriteLine("{0}: created and wrote line", filename);
DateTime t0 = DateTime.UtcNow;
for (;;)
{
Thread.Sleep(16);
if (IsDeletePending(fs))
{
Console.WriteLine("{0}: detected pending delete", filename);
break;
}
if (DateTime.UtcNow - t0 > MAX_WAIT_TIME)
{
Console.WriteLine("{0}: timeout reached with no delete", filename);
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine("{0}: {1}", filename, ex.Message);
}
finally
{
if (fs != null)
{
Console.WriteLine("{0}: closing", filename);
fs.Dispose();
}
}
}
return 0;
}
}
I would use a different signaling mechanism. (I am making the assumption all file access is within your control and not from a closed external program, mainly due to the flags being employed.)
The only "solution" within those bounds I can think of is a poll on file-access and check the exception (if any) you get back. Perhaps there is something much more tricky (at a lower-level than the win32 file API?!?), but this is already going down the "uhg path" :-)
FileSystemWatcher would probably be the closest thing, but it can't detect a "pending" delete; when the file IS deleted, an event will be raised on FileSystemWatcher, and you can attach a handler that will gracefully interrupt your file processing. If the lock (or lack of one) you acquire in opening the file makes it possible for the file to be deleted at all, simply closing your read-only FileStream when that happens should not affect the file system.
The basic steps of a file watcher are to create one, passing an instance of a FileInfo object to the constructor. FileInfos can be created inexpensively by just instantiating one, passing it the path and filename of the file as a string. Then, set its NotifyFilter to the type(s) of file system modifications you want to watch for on this file. Finally, attach your process's event handler to the OnDeleted event. This event handler can probably be as simple as setting a bit flag somewhere that your main process can read, and closing the FileStream. You'll then get an exception on your next attempt to work with the stream; catch it, read the flag, and if it's set just gracefully stop doing file stuff. You can also put the file processing in a seperate worker thread, and the event handler can just tell the thread to die in some graceful method.
If the file is small enough, your application could process a copy of the file, rather than the file itself. Also, if your application needs to know whether the owning process deleted the original file, set up a FileSystemWatcher (FSW) on the file. When the file disappears, the FSW could set a flag to interrupt processing:
private bool _fileExists = true;
public void Process(string pathToOriginalFile, string pathToCopy)
{
File.Copy(pathToOriginalFile, pathToCopy);
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = pathToOriginalFile;
watcher.Deleted += new FileSystemEventHandler(OnFileDeleted);
bool doneProcessing = false;
watcher.EnableRaisingEvents = true;
while(_fileExists && !doneProcessing)
{
// process the copy here
}
...
}
private void OnFileDeleted(object source, FileSystemEventArgs e)
{
_fileExists = false;
}
No, there's no clean way to do this. If you were concerned about other processes opening and/or modifying the file, then oplocks could help you. But if you're just looking for notification of when the delete disposition gets set to deleted, there isn't a straightforward way to do this (sans building a file system filter, hooking the APIs, etc. all of which spooky for an application do be doing w/o very good reason).