Keep Your Revit Plugins Always Up to Date - Automatically

One of the key challenges in Revit plugin development is continuous delivery. A new version of a Revit plugin cannot be loaded while the previous one is still running in the Revit process. As a result, users often remain unaware of available updates until they manually start the plugin and check for them.
In this article, we’ll share how we at atomatiq address this problem and ensure a smoother update experience.
ProJECT STRUCTURE

The Bootstrapper runs the updater, checks for new versions, and then starts the main application.
There is no way to update Revit plugins directly without using a separate application. One option is to create a loader that works silently in the background and, each time Revit closes, checks for updates and applies them. However, we use a different approach - updating during Revit startup.
Typically, our C# solutions for Revit consist of two main projects: the Application and the Bootstrapper. The Bootstrapper includes an .addin file, so when Revit starts, only the Bootstrapper is loaded. The Bootstrapper checks for updates, and if no updates are found, it simply loads the Application. But if updates are available, it launches a standalone Updater .exe application. This updater is independent of Revit - its only purpose is to download, unpack, and replace the old Application files with the latest version. Once the update process is complete, the updated Application is started.
public async void RunApplication()
{
if (_updaterProcess is not null)
{
await Task.Run(() => _updaterProcess.WaitForExit());
}
_handler.Raise(_ =>
{
Starter.Run(application);
});
}
THE UPDATER
First, it displays the key information to the user: the version number of the update and its changelog. Then it asks whether they want to proceed with updating the Application.

When the user confirms, the update process begins:
- The updater downloads the new version (as a .zip file) from cloud storage.
- It unpacks the contents into a temporary folder.
At this point, we have two copies of the application’s addin folder:
- The currently loaded version in Revit’s Addin folder (some files here are locked by Revit and the Updater).
- The newly unpacked version in the Temp folder (unlocked).
The updater then starts copying files one by one from the Temp folder into the Addin folder.
Some files can be replaced successfully, but others are blocked by the running Revit process. For these blocked files, we store them in a dictionary where the processId is the key and the list of locked files is the value.
private void TryCopyFile(string sourceFileName, string destFileName)
{
try
{
var processIds = NativeMethods.FindProcessIds(destFileName);
if (processIds.Count == 0)
{
// file is not blocked -- copy it
File.Copy(sourceFileName, destFileName, true);
}
else
{
foreach (var processId in processIds)
{
// file is blocked -- add it to the map with the blocking process id
if (_processFilesMap.TryGetValue(processId, out var lockedFiles))
{
lockedFiles[sourceFileName] = destFileName;
}
else
{
_processFilesMap.Add(processId, new Dictionary<string, string>
{
{ sourceFileName, destFileName }
});
}
}
}
}
catch
{
// ignored
}
}
The Application inside the Revit Addin folder is updated, but the Bootstrapper and Updater themselves are not replaced at this stage. This is important because we cannot run the Updater directly from the Revit folder - it would lock its own files during the update.
Instead, we execute the Updater from the Temp folder, allowing it to freely update the Application files without blocking itself.
private void UpdateFiles()
{
if (_processFilesMap.Count == 0) return;
foreach (var map in _processFilesMap)
{
var arguments = new Arguments();
arguments.Add("--patch {value}", map.Key);
arguments.Add("--files {value}", map.Value, separator: ' ');
// it will use the latest version in Temp folder
ProcessTasks.StartProcess(Updater.Latest, arguments.Render());
}
}
The Updater running from the Temp folder does not block the files in the Addin folder, which means it can also update the Updater files themselves.
The last component to update is the Bootstrapper. Since it is loaded by Revit, we cannot replace it while Revit is running. To handle this, the Temp-folder Updater waits until Revit is closed, and then updates the Bootstrapper. This works fine because the Bootstrapper’s only role is to update the plugin and load it - so keeping the older version in the Revit folder until shutdown does not affect the process.
As a result:
- After Revit starts up → we have the latest version of the Application and the Updater.
- After Revit shuts down → we also have the latest version of the Bootstrapper.
And that’s it - the plugin is now fully updated!