A common problem that you, as a developer, may run into on Windows is the need to replace a file that is in use. This commonly happens with installations and upgrades, but can of course also happen in general use.
In earlier versions of Windows, when most users worked in full administrator mode, the MoveFileEx function with the MOVEFILE_DELAY_UNTIL_REBOOT flag was suggested by Microsoft as a great approach for updating files that were in use. This flag would, as it sounds, allow you to schedule the move or deletion of a file at a time when it was (pretty much) guaranteed to succeed.
For example:
// This will delete c:\temp\coolcorelibrary.dll on the next reboot
MoveFileEx(“c:\\temp\\coolcorelibrary.dll”, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
Nowadays, of course, this flag does not work unless you are running in the context of an administrative user. That’s great, you think, this will still work for my install or upgrade program.
But don’t trust that feeling of security. Things are never as easy as they seem. I first realised that this was a problem when researching issues occasionally reported by some of our Keyman Desktop users.
Take this scenario:
- A user, Joe, decides to uninstall your awesome app CoolCoreProgram.
- The uninstaller finds that a critical file (let’s call it coolcorelibrary.dll) is in use and can’t delete it
- Installer calls a MoveFileEx with MOVEFILE_DELAY_UNTIL_REBOOT to schedule deletion of coolcorelibrary.dll.
-
Would you click Restart Now? Why not leave it till later? The uninstall completes and presents Joe with the dreaded “Hey, you need to restart Windows now” dialog.
- Poor unhappy Joe swears and cancels the restart, and continues his work. He can’t see any good reason to restart Windows…
- A short while later, Joe realises that he actually loves CoolCoreProgram and so he downloads and reinstalls the latest, greatest version (now with extra shiny!)
- Shortly thereafter, Joe finishes up for the day and turns off his computer.
- The next morning, after Joe starts up his computer, Windows notes its instructions from the previous day, and obediently deletes coolcorelibrary.dll.
- And now Joe is now really, really unhappy when he tries to start CoolCoreProgram and he gets a bizarre coolcorelibrary.dll missing error.
Who is Joe going to blame? Is it his fault for not restarting? Is it yours for using cool APIs such as MoveFileEx? Or is it a woeful confluence of unintended consequences?
This is probably one of the simplest scenarios in which this problem can crop up. Things get much worse when you talk about shared libraries, fonts, or other resources which may be in use by the system, or multi-user systems.
Some reasons I have encountered for files in use:
- Program is still running (duh!)
- System has locked the file (common with fonts, hook libraries)
- Antivirus or security software is scanning the file
- Another application is trying to update the file (i.e. don’t run multiple installers at once)
One possible fix would be to check and update the registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations for listed file before creating or updating them. But that’s a big performance and reliability hit. I can’t say that solution sits well with me.
Another idea is to block further install and upgrade tasks until Joe actually does restart his computer, for example with a registry setting or a RunOnce entry. But that’s going to make Joe hate me even more than he already does!
Some scenarios can be fixed with the Windows Installer automatic repair functionality. But that assumes access to the original source files is always possible. So that’s not a general solution.
I don’t really have a good general solution to this problem. Any ideas?
I’ve run into this too. I would place the responsibility squarely with the program developer (rather than MoveFileEx). The function is straightforward in its operation and documentation (on MSDN). I don’t think checking a registry value a few times is going to impact perf in any noticeable way (try running procmon with a “reg” filter and see how many times that happens in a second). And I think Joe will indeed hate you more if you allow him to bork his system, when it’s preventable. Look at his options: a bit of inconvenience with a ‘synchronous’ reboot required before reinstalling CoolCoreProgram, versus a major headache of calling said program’s tech support to find out what ‘dynalink error’ means.
Thanks for your comment, Scott. You are right that performance is not likely to be an issue for most programs.
A secondary issue with MoveFileEx is of course that it only works with an elevated administrator token, so it’s really only good for installers and their ilk.
Rename the DLL file first (coolcorelibrary.old), and then mark it for deletion. You can rename a DLL while loaded into a process, and applications will continue using the original DLL until it is unloaded, while any new applications will use the new one.
Thanks for reading and commenting, Dwayne. 🙂 Yes, in most cases you can rename a DLL, although not fonts. Unfortunately you now have two different versions of the DLL loaded into memory, and these may use shared memory or communicate in some way to share internal data structures, which may have changed between those versions. Then you get weird crashes until you reboot and your user still hates you 🙁
Any installer worth its salt will check for pending operations and refuse to install a program until the previous setup finishes.
What you are bringing up is a caveat for “roll your own” setups, but it is largely a non-issue.
Thanks for the comment Igor. Yes, “roll your own” installers tend to be where this problem arises, nowadays. Back in pre-Vista days, lots of apps used MoveFileEx because “everyone” had Admin permissions, and most installers would not enforce a reboot for pending file rename operations before continuing. Even today, I wonder how many installers enforce that (and how many users try to circumvent the restart).
Back when I wrote this, it was because it was an issue in an app I had to maintain… I agree it’s less of an issue now.
I’m using Inno Setup with the same problem, it refuse to do the installation when detects a previous pending deletion about the same file.
My solution is to replace all the conflict files with an non-exist path (eg, %temp%), that works fine.