My Notebook

A platform independent way for a C# program to update itself

Author
Date
Category
Programming/C#

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