In Windows you cannot write to an executable file while it is still running, because the file is locked by the file system. Therefore a running application can never delete or update itself without the help of another process that does the actual work. However, this rule doesn't apply to Batch-scripts. They are interpreted line by line by cmd.exe
and can delete themselves. This post demonstrates a simple trick how you can use this fact to generate a temporary self-removing Batch-script in C# to update your application.
The Batch-script
@ECHO OFF
TIMEOUT /t 1 /nobreak > NUL
TASKKILL /IM "App.exe" > NUL
MOVE "App_Update.exe" "App.exe"
DEL "%~f0" & START "" /B "App.exe"
The TIMEOUT
command waits for one second, to allow the application to close itself. If it is still running after that, the TASKKILL
command makes sure it is killed. The MOVE
command replaces the old version of the application with the new and updated one.
The last line is a bit more complicated. It executes two commands after each other. First the Batch-file deletes itself, then it starts the updated application. The command START "" /B
is used so that the command line interpreter can terminate immediately and doesn't wait for App.exe to finish.
Command-line Arguments
The parameters for a Batch-file can be accessed by using the percent sign %
followed by the numerical position of the parameter. For example, the first parameter would be %1
the second %2
and so on. %0
is the actual command used to execute the Batch-file.
Parameter Extensions
Batch-scripts offer a number of extensions that can be used with any of the numbered parameters from above. This article has a nice overview of all of the possible extensions. We are only interested in the %~f<number>
extension, which expands a parameter to a fully qualified absolute path.
So if %0
contains "MyBatchFile"
, then %~f0
expands it to "C:\Users\Test\MyBatchFile.bat"
.
The C# Update Function
public static void UpdateSelf(byte[] buffer) {
var self = System.Reflection.Assembly.GetExecutingAssembly().Location;
if (Environment.OSVersion.Platform == PlatformID.Unix ||
Environment.OSVersion.Platform == PlatformID.MacOSX) {
File.WriteAllBytes(self, buffer);
Process.Start (self);
// Sleep for half a second to avoid an exception
Thread.Sleep(500);
Environment.Exit(0);
} else {
var selfFileName = Path.GetFileName(self);
var selfWithoutExt = Path.Combine(Path.GetDirectoryName(self),
Path.GetFileNameWithoutExtension(self));
File.WriteAllBytes(selfWithoutExt + "Update.exe", buffer);
using (var batFile = new StreamWriter (File.Create (selfWithoutExt + "Update.bat"))) {
batFile.WriteLine ("@ECHO OFF");
batFile.WriteLine ("TIMEOUT /t 1 /nobreak > NUL");
batFile.WriteLine ("TASKKILL /IM \"{0}\" > NUL", selfFileName);
batFile.WriteLine ("MOVE \"{0}\" \"{1}\"", selfWithoutExt + "Update.exe", self);
batFile.WriteLine ("DEL \"%~f0\" & START \"\" /B \"{0}\"", self);
}
ProcessStartInfo startInfo = new ProcessStartInfo(selfWithoutExt + "Update.bat");
// Hide the terminal window
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
startInfo.WorkingDirectory = Path.GetDirectoryName(self);
Process.Start (startInfo);
Environment.Exit(0);
}
}
The function void UpdateSelf()
updates the currently running executable with whatever is in the buffer it gets as a parameter. Since Unix systems do not lock executable files, the update is straight forward. For Windows systems it uses the trick with the Batch-file from above. The result is a simple platform independent self-update function.
By setting startInfo.CreateNoWindow = true
and startInfo.UseShellExecute = false
we can hide the terminal window, so that it doesn't flash briefly on the screen.
Demo Project
The following ZIP-file contains a simple demo project. I've so far only tested it with monodevelop, but it should be compatible with Visual Studio. Please leave a comment, if you have problems importing it.
References
- Batch Parameter Reference - http://ss64.com/nt/syntax-args.html