One of the side effects of needing a temporary directory is needing to clean up after yourself. In my case, I extract a plugin into its own temporary directory (though it's currently not sandboxed) and load assemblies (kinda like ELF files) from that directory. When my plugin objects are disposed of (IDisposable and all that), the directory needs to be deleted.
Sadly, the .NET runtime still holds a handle to your assembly file while it's loaded into your application. This means you're met with an exception when you try to delete the directory because a file in that directory is in use.
There are many ways to delete an executable currently in use, so I thought I'd share my simple implementation.
The idea is simple: run a child process which waits for the parent process to quit. Then, delete the needed files and directories.
The program is so simple it fits nicely into one or two functions:
static void Main(string[] args) { try { var parentProcess = Process.GetCurrentProcess().Parent(); parentProcess.WaitForExit(5000); // Wait some reasonable amount of time. } catch (Exception e) { // We don't care about no exceptions! Console.WriteLine("Exception while waiting for parent process:"); Console.WriteLine(e); } foreach (string fileName in args) { try { if (Directory.Exists(fileName)) { Directory.Delete(fileName, true); } else if (File.Exists(fileName)) { File.Delete(fileName); } } catch (Exception e) { // We don't care about no exceptions! Console.WriteLine("Exception while deleting file or directory '{0}':", fileName); Console.WriteLine(e); } } }
I call this program DOPE: Delete On Parent Exit.
I provide a sensible timeout for waiting for the parent process in case problems occur. I don't want to leave DOPE running forever on a user's machine or something. Windows is not to be trusted with process management...
Oddly, the .NET framework does not provide a mechanism to easily grab the parent of the current process, so StackOverflow saves the day with several solutions. I went with the accepted solution (changing the ugly Parent method name to GetParent). Something in C or C++ with a POSIX/WinApi switch may be more appealing to you, but my application is mostly locked to Windows anyway so it wasn't a problem for me.
Invoking this process is kind of weird. I haven't found an elegant solution quite yet. I've been considering storing the DOPE executable as a resource in my application and extracting it to yet another temporary directory, but that would require deleting DOPE itself which brings us almost back to square one. Injecting a running process with the new code could work, but that's too much of a hack for me to deal with and it would probably require unmanaged code.
I decided to distribute DOPE along-side my main executable. This leads to the hack of running DOPE by getting the executable's directory and appending "DOPE.exe", as shown below:
private static void DeleteLater(string path) { Process.Start(new ProcessStartInfo { FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "DOPE.exe"), // FIXME Better way to get EXE path Arguments = string.Format("\"{0}\"", path), // FIXME SECURITY !!! ErrorDialog = false, UseShellExecute = false, CreateNoWindow = true, }); }
As you can see by the comment in the code, there's a possible security exploit; a file name of asdf" "c:\windows" " would cause DOPE to try to delete c:\windows. Then again, in my case I'm getting my directory name from the operating system, and Windows prohibits " in file names, so I should be safe. (Right?)
If you omit setting the last two properties to the ProcessStartInfo object, you'll get an ugly cmd.exe window, making the user think they have a virus and stealing their keyboard focus. Don't omit those lines, please.
Comments
Post new comment