„Bei jedem Update machen wir eine Durchsage über die Gebäude-PA-Anlage und bitten alle, die App mit dem X-Button zu schließen.“ „Wir haben einen Batch-Job eingerichtet, der die exe auf dem Dateiserver über Nacht austauscht, und am Morgen war er mit ‚der Prozess kann nicht auf die Datei zugreifen’ fehlgeschlagen.“ In Beratungen zur Verteilung und Aktualisierung von Geschäftsanwendungen taucht dieses Problem „Datei wird verwendet“ ständig auf. Die Meldung „ein Neustart ist erforderlich“, die ein Installer am Ende anzeigt, hat dieselbe Wurzel: Alles führt darauf zurück, dass sich eine Datei, die jemand hält, nicht austauschen lässt.
Was die Sache erschwert, ist, dass das Problem nur manchmal auftritt. Auf einem Entwicklungsrechner schließt man die App vor dem Update selbst, sodass es sich nie zeigt; in einer gemeinsam genutzten Produktivumgebung genügt es, dass eine Person die App beim Feierabend offen gelassen hat, um das gesamte nächtliche Update zunichtezumachen. Und die Fehlermeldung verrät nichts darüber, wer die Datei hält.
Windows besitzt tatsächlich einen dafür vorgesehenen Standardmechanismus des Betriebssystems: die Restart-Manager-API, die auflistet, „wer die Datei hält“, diese höflich beendet und nach dem Update sogar neu startet. Und indem man die Eigenschaft nutzt, dass „eine laufende exe umbenannt werden kann“, lässt sich auch ein Update entwerfen, das ab dem nächsten Start ohne das Anhalten irgendeines Prozesses zur neuen Version wechselt. Dieser Artikel richtet sich an Entwickler, die mit „die Datei wird verwendet“ in automatischen Updatern und Installern von Geschäftsanwendungen ringen, und behandelt die Mechanik der Sperre, die Verwendung von Restart Manager, das Verhalten, das die Anwendung selbst einhalten sollte, sowie Implementierungsmuster für automatische Updates — gestützt auf die offizielle Dokumentation und mit C#-Diagnosecode.
1. Das Wichtigste zuerst
- Eine laufende exe oder eine geladene DLL lässt sich nicht überschreiben oder löschen, kann aber innerhalb desselben Datenträgers umbenannt (verschoben) werden. Die alte Datei auf einen Ausmusterungsnamen umzubenennen und dann die neue Datei zu platzieren — Umbenennen-dann-Ersetzen — ist die Grundform eines selbstgebauten Updaters.
- Restart Manager ist eine Standard-API des Betriebssystems ab Windows Vista, die für genau das Problem „Datei wird verwendet“ existiert. Registrieren Sie die zu aktualisierenden Dateien, und sie listet die Anwendungen und Dienste auf, die sie halten, und übernimmt Beenden und Neustart. Ihr Zweck ist es, OS-Neustarts zu verringern oder ganz zu vermeiden.1
- Der API-Ablauf lautet RmStartSession → RmRegisterResources → RmGetList → RmShutdown → (Update) → RmRestart → RmEndSession. Das Beenden erfolgt in der Reihenfolge GUI-Apps → Konsolen-Apps → Dienste → Explorer, der Neustart in umgekehrter Reihenfolge.12
- RmGetList allein genügt bereits, um ein Diagnosewerkzeug zu bauen, das meldet, „wer das hier hält“. Ein paar Dutzend Zeilen P/Invoke aus C# (siehe den Code unten).3
- MSI (Windows Installer ab 4.0) verwendet Restart Manager automatisch. Fügen Sie Ihrem Paket den Dialog MsiRMFilesInUse hinzu, können Sie Benutzern die Option anbieten, „Anwendungen automatisch zu schließen und neu zu starten“.4
- Die Anwendung hat ein eigenes Verhalten einzuhalten. Registrieren Sie eine Neustart-Befehlszeile mit RegisterApplicationRestart, reagieren Sie auf WM_QUERYENDSESSION (lParam=ENDSESSION_CLOSEAPP), und speichern Sie ungesicherte Daten in WM_ENDSESSION, bevor Sie sich beenden. Eine App, die diese drei Dinge implementiert, „wird für ein Update geschlossen und kommt danach wieder in ihrem ursprünglichen Zustand hoch“.56
- Um Neustartschleifen zu vermeiden, wird eine App, die vor weniger als 60 Sekunden gestartet wurde, nicht neu gestartet. Außerdem beträgt das Timeout für ein erzwungenes Beenden durch Restart Manager 30 Sekunden für Anwendungen und 20 Sekunden für Dienste.62
- Ist ein Austausch wirklich unmöglich, ist die letzte Möglichkeit MoveFileEx + MOVEFILE_DELAY_UNTIL_REBOOT (den Austausch für den nächsten OS-Neustart planen). Es setzt Administratorrechte voraus, und die Reservierung wird in der Registrierung unter PendingFileRenameOperations vermerkt.7
2. Warum sich eine verwendete Datei nicht austauschen lässt — und die Lücke, dass „Umbenennen trotzdem funktioniert“
Führt Windows eine exe aus oder lädt eine DLL, bildet das Betriebssystem diese Datei als speicherabgebildete Datei (einen Image-Abschnitt) ab. Da der Dateiinhalt während der Ausführung weiterhin referenziert wird, sind als Konsequenz aus der Funktionsweise des Paging Änderungen des Inhalts und das Löschen blockiert. Das ist das Verhalten, das Sie als „ein anderer Prozess verwendet diese Datei“ beim Kopieren im Explorer sehen, als IOException (Freigabeverletzung) von File.Copy, oder als UnauthorizedAccessException von File.Delete.
Hier kommt eine wenig bekannte Eigenschaft von Windows ins Spiel, die entscheidend ist. Selbst wenn der „Inhalt“ der Datei gesperrt ist, lässt sich der „Name“ im Verzeichnis ändern. Auch bei einer laufenden exe gelingt ein Umbenennen (Verschieben), solange es innerhalb desselben Datenträgers bleibt — denn was das ausgeführte Image hält, ist die Datei selbst, nicht der Pfadname.
Aus dieser Asymmetrie folgt die Grundform des selbstgebauten Updates, das Muster Umbenennen-dann-Ersetzen.
1. MyApp.exe (laufend) in MyApp.exe.old umbenennen <- gelingt auch bei laufender Anwendung
2. Die neue MyApp.exe am ursprünglichen Pfad platzieren <- gelingt, weil der Name jetzt frei ist
3. Ab dem nächsten Start wird die neue MyApp.exe verwendet
4. Die .old-Datei zu einem späteren Zeitpunkt löschen, nachdem der alte Prozess beendet wurde
(kann während der Ausführung nicht gelöscht werden, daher beim nächsten Update oder Start aufräumen)
Der Wert dieses Musters ist, dass es „die neue Version ab dem nächsten Start“ liefert, ohne den Prozess anzuhalten, und Update-Frameworks wie der Updater von Chrome sowie Squirrel/Velopack bauen im Kern auf dieser Eigenschaft auf. Drei Vorbehalte: Umbenennen funktioniert nur innerhalb desselben Datenträgers (ein Verschieben auf einen anderen Datenträger wird zu einem Kopieren plus Löschen, und das Löschen schlägt fehl); das Aufräumen der .old-Dateien muss Teil des Designs sein; und weil „der gerade laufende alte Prozess“ als alte Version weiterläuft, gibt es ein Zeitfenster, in dem alte und neue Prozesse koexistieren.
Wenn Sie im Rahmen einer Störungsanalyse von Hand herausfinden möchten, „wer die Datei überhaupt hält“, ist der schnellste Weg, mit Process Explorer oder dem Handle-Befehl nach Handles und geladenen DLLs zu suchen. Dieses Vorgehen ist im am selben Tag veröffentlichten Schwesterartikel „Hängern und Lecks mit Process Explorer, Handle und VMMap nachjagen“ beschrieben. In diesem Artikel tun wir dasselbe aus dem Code heraus — das heißt, wir verwenden Restart Manager, um es in den Updater selbst einzubauen.
3. Wie Restart Manager funktioniert — Von RmStartSession bis RmRestart
Restart Manager ist ein Satz von APIs (rstrtmgr.dll), der standardmäßig ab Windows Vista / Windows Server 2008 ausgeliefert wird, und sein Zweck steht ganz oben in der Dokumentation klar formuliert: Der Hauptgrund, warum eine Installation oder ein Update einen Systemneustart erfordert, ist, dass die zu aktualisierenden Dateien von laufenden Anwendungen und Diensten verwendet werden, und Restart Manager verringert oder vermeidet diesen Neustart, indem es alle bis auf die kritischen Anwendungen und Dienste beendet und wieder startet.1 Sie haben sicher schon den Dialog gesehen, der während einer MSI-Installation Prozesse auflistet — „die folgenden Anwendungen verwenden Dateien, die…“ —, und auch die Installer und Updater großer Produkte wie Visual Studio und Office nutzen diesen Mechanismus.
Der API-Ablauf ist geradlinig.
| Schritt | Funktion | Was sie tut |
|---|---|---|
| 1 | RmStartSession | Startet eine Sitzung und liefert ein Handle sowie einen Sitzungsschlüssel (eine GUID-Zeichenfolge) zurück |
| 2 | RmRegisterResources | Registriert die zu aktualisierenden Dateipfade (auch Prozess- und Dienstnamen werden akzeptiert) |
| 3 | RmGetList | Listet die Anwendungen und Dienste auf, die die registrierten Ressourcen verwenden3 |
| 4 | RmShutdown | Beendet sie (standardmäßig höflich, bei entsprechender Angabe erzwungen)2 |
| 5 | — | Die Dateien in diesem Zeitfenster austauschen |
| 6 | RmRestart | Startet die für den Neustart registrierten Anwendungen neu |
| 7 | RmEndSession | Schließt die Sitzung |
Einige Spezifikationspunkte sind erwähnenswert.
- Reihenfolge beim Beenden und beim Neustart. Das Beenden erfolgt in der Reihenfolge GUI-Apps → Konsolen-Apps → Dienste → Explorer, und nach dem Update werden die registrierten Anwendungen in umgekehrter Reihenfolge neu gestartet.1
- Das Beenden erfolgt „zuerst höflich“. GUI-Apps erhalten WM_QUERYENDSESSION/WM_ENDSESSION (lParam=ENDSESSION_CLOSEAPP), und Apps, die nicht reagieren, erhalten zusätzlich WM_CLOSE. Konsolen-Apps erhalten CTRL_C_EVENT, und Dienste werden über den SCM gestoppt. Selbst wenn RmForceShutdown angegeben wird, wird zunächst ein sanftes Beenden versucht, und nicht reagierende Apps werden nach 30 Sekunden (20 Sekunden bei Diensten) zwangsweise beendet.52
- Nur über RegisterApplicationRestart registrierte Anwendungen können neu gestartet werden. Die Angabe von RmShutdownOnlyRegistered bei RmShutdown liefert das sicherere Verhalten, „nur zu beenden, wenn sich alle für den Neustart registriert haben“.25
- Ein Beenden über Sitzungsgrenzen hinweg ist nicht möglich. Ein als LocalSystem-Dienst laufender Installer kann Anwendungen, die in einer Benutzersitzung laufen, weder beenden noch neu starten. Das ist der Punkt, an dem die meisten beim Entwerfen unbeaufsichtigter nächtlicher Updates stolpern, und es erfordert Maßnahmen wie das gesonderte Starten eines Prozesses innerhalb der Zielsitzung, der Beenden und Neustart übernimmt.2
- Kritische Systemdienste und kritische Prozesse liegen außerhalb des Geltungsbereichs. In diesem Fall wird eine Feststellung zurückgegeben, dass ein OS-Neustart erforderlich ist (RM_REBOOT_REASON).13
Es lohnt sich auch, die Beziehung zu MSI klarzustellen. MSI-Pakete ab Windows Installer 4.0 verwenden Restart Manager automatisch. Das Standardverhalten lautet „statt das Betriebssystem neu zu starten, Anwendungen nach Möglichkeit beenden und neu starten“. Was ein Paketautor tun kann, ist: den Dialog MsiRMFilesInUse hinzufügen, der dem Benutzer bei einer Installation mit vollständiger UI die Option „Anwendungen automatisch schließen und neu zu starten versuchen“ anbietet (mit Rückfall auf den herkömmlichen FilesInUse-Dialog bei älteren Installern); das Verhalten mit Eigenschaften wie MSIRESTARTMANAGERCONTROL steuern; und zusätzliche Ressourcen registrieren, indem eine Custom Action über die Eigenschaft MsiRestartManagerSessionKey RmJoinSession aufruft (wobei diese Custom Action vor der Aktion InstallValidate eingeordnet werden muss, an der Dateien in Verwendung erkannt werden). Bei einer stillen Installation wird Restart Manager immer verwendet, und Anwendungen werden automatisch beendet.4
Mit anderen Worten: Verteilen Sie über MSI, müssen Sie selten selbst „Code schreiben, der Restart Manager aufruft“ — die eigentliche Arbeit besteht darin, das Verhalten der Anwendung (der übernächste Abschnitt) richtig hinzubekommen. Die API direkt aufzurufen lohnt sich nur, wenn Sie einen selbstgebauten Updater schreiben. Die Wahl der Verteilungsmethode an sich wird in „Eine Windows-App-Verteilungsmethode wählen - MSI/MSIX/ClickOnce/xcopy/eigener Updater“ behandelt.
4. Melden, „wer es hält“ — in C#: RmGetList-Diagnosecode
Innerhalb von Restart Manager ist RmGetList der Teil, der auch für Leute nützlich ist, die nie Update-Logik schreiben. Weil Sie „die Liste der Prozesse und Dienste, die diese Datei verwenden“ von einer API erhalten können, lässt sich die Fehlermeldung Ihres Updaters von „die Datei wird verwendet“ zu „MyApp.exe (PID 4132) auf dem Buchhaltungsterminal hält sie“ ändern. Als P/Invoke aus C# geschrieben sieht das so aus (funktioniert sowohl unter .NET Framework 4.8 als auch unter .NET 8).
// WhoLocks.cs — auflisten, "wer" eine bestimmte Datei über Restart Manager hält
// Verwendung: WhoLocks.exe C:\App\MyApp.exe C:\App\MyLib.dll
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
internal static class Program
{
private const int CCH_RM_SESSION_KEY = 32; // der Sitzungsschlüssel ist eine GUID-Zeichenfolge (32 Zeichen + Terminator)
private const int CCH_RM_MAX_APP_NAME = 255;
private const int CCH_RM_MAX_SVC_NAME = 63;
private const int ERROR_MORE_DATA = 234;
[StructLayout(LayoutKind.Sequential)]
private struct RM_UNIQUE_PROCESS
{
public uint dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
// RM_APP_TYPE: 1=GUI-App, 2=anderes Fenster, 3=Dienst, 4=Explorer, 5=Konsole, 1000=kritisch
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
public string strServiceShortName;
public int ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
// Die Restart-Manager-APIs geben den Win32-Fehlercode direkt als Rückgabewert zurück, nicht über GetLastError
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmStartSession(
out uint pSessionHandle, int dwSessionFlags, StringBuilder strSessionKey);
[DllImport("rstrtmgr.dll")]
private static extern int RmEndSession(uint dwSessionHandle);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmRegisterResources(uint dwSessionHandle,
uint nFiles, string[] rgsFileNames,
uint nApplications, RM_UNIQUE_PROCESS[] rgApplications,
uint nServices, string[] rgsServiceNames);
[DllImport("rstrtmgr.dll")]
private static extern int RmGetList(uint dwSessionHandle,
out uint pnProcInfoNeeded, ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps, out uint lpdwRebootReasons);
private static void Main(string[] args)
{
if (args.Length == 0)
{
Console.Error.WriteLine("Usage: WhoLocks <file path> ...");
Environment.Exit(2);
}
var sessionKey = new StringBuilder(CCH_RM_SESSION_KEY + 1);
int rc = RmStartSession(out uint session, 0, sessionKey);
if (rc != 0) throw new Win32Exception(rc, $"RmStartSession failed (rc={rc})");
try
{
// Der API-Vertrag verlangt, dass registrierte Dateinamen vollständige Pfade sind.
// Vor der Registrierung normalisieren, damit es auch bei relativen Pfaden funktioniert
var fullPaths = Array.ConvertAll(args, Path.GetFullPath);
rc = RmRegisterResources(session, (uint)fullPaths.Length, fullPaths, 0, null, 0, null);
if (rc != 0) throw new Win32Exception(rc, $"RmRegisterResources failed (rc={rc})");
// Die benötigte Anzahl erfragen -> Array zuweisen -> abrufen. Kommen zwischen den beiden
// Aufrufen weitere Prozesse hinzu, kommt erneut ERROR_MORE_DATA zurück, also in einer
// Schleife wiederholen
uint count = 0;
uint rebootReasons;
RM_PROCESS_INFO[] apps = null;
while (true)
{
rc = RmGetList(session, out uint needed, ref count, apps, out rebootReasons);
if (rc == 0) break;
if (rc != ERROR_MORE_DATA) throw new Win32Exception(rc, $"RmGetList failed (rc={rc})");
count = needed;
apps = new RM_PROCESS_INFO[needed];
}
Console.WriteLine($"Processes/services in use: {count} (RebootReasons={rebootReasons})");
for (int i = 0; i < count; i++)
{
RM_PROCESS_INFO a = apps[i];
Console.WriteLine(
$" PID={a.Process.dwProcessId,-6} Type={a.ApplicationType} " +
$"Restartable={a.bRestartable} Session={a.TSSessionId} " +
$"Name={a.strAppName} Service={a.strServiceShortName}");
}
}
finally
{
RmEndSession(session); // die Sitzung immer in einem finally-Block freigeben
}
}
}
Drei zentrale Punkte sind zu beachten. Erstens: Die Restart-Manager-APIs geben den Win32-Fehlercode direkt als Rückgabewert zurück und verwenden nicht GetLastError (es ist kein SetLastError = true bei DllImport nötig). Zweitens folgt RmGetList der Aufrufkonvention „ist der Puffer zu klein, ERROR_MORE_DATA (234) zusammen mit der benötigten Anzahl zurückgeben“, weshalb Sie es als Schleife Erfragen → Zuweisen → Abrufen schreiben.3 Drittens: Ist lpdwRebootReasons etwas anderes als 0 (RmRebootReasonNone), ist das eine Feststellung, dass „das Beenden von Anwendungen nicht genügt — ein OS-Neustart ist erforderlich“.3 Wie man Zeichenfolgen in Strukturen mit ByValTStr marshaliert und wie man diese Art von P/Invoke allgemein sicher schreibt, wird in „Win32-APIs sicher aus C# aufrufen — Ein praktischer P/Invoke-Leitfaden“ behandelt.
Fügen Sie diesem Code zwei weitere P/Invokes für RmShutdown und RmRestart hinzu, und Sie haben das Grundgerüst eines selbstgebauten Updaters: auflisten → höflich beenden → austauschen → neu starten. Beachten Sie jedoch, dass RmShutdown nur vom Prozess aufgerufen werden darf, der selbst RmStartSession aufgerufen hat, und nicht aus einer MSI-Custom-Action aufgerufen werden darf (weil MSI die Sitzung selbst verwaltet).4
5. Das Verhalten der Anwendung — Das Dreiteilige-Paket für „höflich geschlossen werden und danach wieder in seinem ursprünglichen Zustand hochfahren“
Weder Restart Manager noch MSI töten Prozesse rundweg mit einem Terminate (sofern kein erzwungenes Beenden angegeben ist). Reagiert die Anwendung nicht, landen Sie wieder beim „Dateien werden verwendet“-Dialog, der den Benutzer belästigt. Um eine Geschäftsanwendung „update-freundlich“ zu machen, implementieren Sie auf Anwendungsseite das folgende Dreiteilige-Paket.5
(1) Mit RegisterApplicationRestart für den Neustart registrieren. Rufen Sie es einmal unmittelbar nach dem Start auf. Restart Manager kann nur registrierte Anwendungen neu starten, und dies ist der einzige Weg, dem Betriebssystem die beim Neustart zu verwendende Befehlszeile mitzuteilen. Die wichtigsten Vorgaben: Der Name der exe darf nicht in der Befehlszeile enthalten sein (das Betriebssystem fügt ihn hinzu), die maximale Länge ist RESTART_MAX_CMD_LINE, und die App wird in den ersten 60 Sekunden nach dem Start nicht neu gestartet, um Neustartschleifen zu vermeiden. Möchten Sie keine Neustarts nach einem Absturz oder Aufhängen, geben Sie RESTART_NO_CRASH | RESTART_NO_HANG an, um es auf „Neustart nur bei Update“ einzugrenzen.6
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern int RegisterApplicationRestart(string commandLine, int flags);
// Beim Start registrieren. Das Flag "/restored" erlaubt es, nach einem Neustart in die
// Wiederherstellungsbehandlung zu verzweigen
// RESTART_NO_CRASH(1) | RESTART_NO_HANG(2) = nur bei einem durch ein Update verursachten Beenden neu starten
RegisterApplicationRestart("/restored", 1 | 2);
(2) Auf WM_QUERYENDSESSION reagieren (lParam=ENDSESSION_CLOSEAPP). Restart Manager verwendet diese Nachricht vor dem Beenden, um zu fragen: „darf ich dich schließen?“. Beenden Sie sich an dieser Stelle noch nicht — geben Sie TRUE zurück, wenn Sie bereit sind (andere Anwendungen sind vielleicht noch nicht bereit). Die Rückgabe von FALSE bricht das Beenden ab, aber da Sie bei einem erzwungenen Beenden ohnehin geschlossen werden, vermeiden Sie Designs, die sich auf FALSE verlassen. Dies ist auch Ihre letzte Gelegenheit, RegisterApplicationRestart im Kontext eines Updates erneut aufzurufen. Registrieren Sie sich mit dem für die Wiederherstellung benötigten Zustand (etwa der ID des geöffneten Berichts), eingebettet in die Befehlszeilenargumente, erneut, kommen Sie nach dem Neustart bis zum „ursprünglichen Bildschirm“ zurück.56
(3) Ungesicherte Daten in WM_ENDSESSION speichern und sich beenden. Die eigentliche Anweisung zum Beenden trifft als WM_ENDSESSION (wParam=TRUE, lParam=ENDSESSION_CLOSEAPP) ein. Schließen Sie innerhalb des Timeouts (bei erzwungenem Beenden 30 Sekunden für Anwendungen) das automatische Speichern ungesicherter Daten, die Serialisierung des Arbeitszustands und das Schließen von Verbindungen ab, und beenden Sie sich dann. Die offiziellen Richtlinien empfehlen, „von vornherein regelmäßig Benutzerdaten zu speichern“. Bei einer Konsolenanwendung trifft statt der WM_-Nachrichten CTRL_C_EVENT ein, weshalb Sie dasselbe über SetConsoleCtrlHandler tun (Console.CancelKeyPress in C#).52
In WPF/WinForms sind Application.SessionEnding / Form.FormClosing (CloseReason.WindowsShutDown) die Einstiegspunkte, aber um das ENDSESSION_CLOSEAPP-Flag selbst zu sehen (die Unterscheidung zwischen einem OS-Neustart und „nur die App schließen und später neu starten“) ist ein Hook in die Fensterprozedur nötig. Überprüfen Sie außerdem im selben Zug Ihr Design des Single-Instance-Mutex, damit der neu gestartete Prozess nicht mit „der eigenen vorherigen Instanz, die noch beendet wird“ kollidiert („Mehrfache Instanzen einer Windows-App verhindern“).
Ist dieses Dreiteilige-Paket vorhanden, ändert sich das MSI-Update-Erlebnis von „per Durchsage alle bitten, die App zu schließen“ zu „das Update läuft, die App schließt sich selbst, und nach dem Update kehrt sie automatisch zu dem Bildschirm zurück, auf dem sie war“. Es ist derselbe Mechanismus, der hinter dem Verhalten von Office steckt, nach einem Neustart Ihre Dokumente wieder zu öffnen.
6. Implementierungsmuster für automatische Updates — ein separater Prozess, Umbenennen-dann-Ersetzen und Ersetzen-beim-Neustart
Hier sind die Entwurfsmuster für den Bau eines eigenen automatischen Updates. Es gibt eine übergeordnete Voraussetzung: Sie können sich selbst (die laufende exe) nicht von innen heraus überschreiben, sodass die Update-Arbeit irgendwohin ausweichen muss — in „einen anderen Prozess“ oder „einen anderen Namen“.
Muster A: Ein separater Updater-Prozess, der wartet und dann austauscht. Erkennt die Hauptanwendung ein Update, startet sie die Updater-exe (oder eine Kopie davon an einem temporären Ort) und beendet sich. Der Updater wartet auf das Beenden des Hauptprozesses (Process.WaitForExit, oder das Warten auf die Freigabe eines Mutex), tauscht die Dateien aus und startet die Hauptanwendung neu. Es ist naiv und verlässlich, aber für Fälle, in denen „die Hauptanwendung lange zum Beenden braucht“ oder „es in einer Multiprozess-Architektur viele zu erwartende Prozesse gibt“, funktioniert eine Kombination mit dem RmGetList des vorherigen Abschnitts zur Identifizierung der Halter und RmShutdown zum Schließen gut.
Muster B: Umbenennen-dann-Ersetzen (Update ohne irgendetwas anzuhalten). Wie in Abschnitt 2 benennen Sie die laufende exe/DLL um, platzieren die neue Version und erhalten „die neue Version ab dem nächsten Start“. Der Vorteil ist, dass die Arbeit des Benutzers nicht unterbrochen wird, was zu residenten, langlaufenden Geschäftsanwendungen passt. Auch .NET-Update-Frameworks wie Squirrel und Velopack nutzen diese Eigenschaft — in Form von Ordnern pro Version plus dem Austausch der Einstiegs-exe — und stellen „das Update im Hintergrund anwenden, ab dem nächsten Start die neue Version“ als fertiges Framework bereit. Von Hand umgesetzt läuft es auf Folgendes hinaus: herunterladen → verifizieren → extrahieren → beiseite umbenennen → platzieren → die ausgemusterten Dateien beim nächsten Start aufräumen.
Muster C: MoveFileEx + MOVEFILE_DELAY_UNTIL_REBOOT (Austausch beim Neustart). Für Dinge, die Sie wirklich nicht anhalten können und denen Sie auch nicht mit einem Umbenennen ausweichen können — den Host-Prozess eines Dienstes, eine Shell-Erweiterung — ist die letzte Möglichkeit, den Austausch für den nächsten OS-Neustart zu planen. Die Reservierung wird in der Registrierung unter HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations vermerkt und in Registrierungsreihenfolge früh im nächsten Startvorgang ausgeführt (unmittelbar nach AUTOCHK, vor dem Anlegen der Auslagerungsdatei). Verwenden Sie es im Bewusstsein, dass es nur von einem Mitglied der Administratorengruppe oder mit LocalSystem-Rechten aufgerufen werden kann; dass es sich nicht mit MOVEFILE_COPY_ALLOWED kombinieren lässt (also auf denselben Datenträger beschränkt ist); dass Sie bei bereits vorhandener Zieldatei zusätzlich MOVEFILE_REPLACE_EXISTING benötigen (ohne das gelingt die Reservierung, aber der Austausch beim Neustart findet nicht statt); und dass der Erfolg der Funktion bedeutet, „die Reservierung ist gelungen“, und nichts über den tatsächlichen Austausch aussagt. Wenn ein Installer sagt „ein Neustart ist erforderlich“, ist genau das im Hintergrund eingereiht.7
Zwei Vorsichtsmaßnahmen gelten für jedes Muster. Die erste ist das Aktualisieren von Diensten: Ein Windows-Dienst kann sich nicht selbst austauschen, weshalb der Standardansatz darin besteht, einen Update-Prozess (oder einen dedizierten Update-Dienst) auszulagern, der „Stoppen über den SCM → Austauschen → Starten“ durchführt (zu den Grundlagen des Dienst-Designs siehe „Wie man Windows-Dienste baut und betreibt“). Die zweite ist das Verifizieren der Update-Dateien. Einen eigenen Austauschmechanismus zu bauen bedeutet auch, einen eigenen „Mechanismus zur Verteilung und Ausführung beliebiger exe-Dateien“ zu bauen, und das Auslassen der Signaturprüfung oder des Schutzes des Download-Pfads macht den Update-Mechanismus selbst zu einem Angriffsweg. Dieses Thema wird gesondert in „Sicherheitsdesign für automatische Updates - Warum HTTPS allein nicht genügt“ behandelt.
7. Entscheidungstabelle für Update-Ansätze
| Ansatz | Wann er passt | Vorbehalte |
|---|---|---|
| MSI + einen OS-Neustart akzeptieren | Niedrige Verteilungsfrequenz (einige Male jährlich); ein Wartungsfenster ist über Nacht oder am Wochenende verfügbar | Am einfachsten zu implementieren. Aber Sie zeigen Benutzern weiterhin „ein Neustart ist erforderlich“ und verlassen sich auf Ersetzen-beim-Neustart (PendingFileRenameOperations)7 |
| MSI + Restart-Manager-Integration (MsiRMFilesInUse + das Dreiteilige-Paket der Anwendung) | Sie möchten die MSI-Verteilung beibehalten, aber das Update-Erlebnis verbessern; standardmäßige, intern verteilte Apps | Die Installer-Seite ist fast vollständig automatisch. Der Effekt hängt von der Implementierung von RegisterApplicationRestart/WM_QUERYENDSESSION in der Anwendung ab45 |
| Selbstgebauter Updater (separater Prozess + Umbenennen-dann-Ersetzen, einschließlich Squirrel/Velopack) | Hohe Update-Frequenz (wöchentlich oder öfter); Benutzer haben keine Administratorrechte; die Arbeit der Benutzer darf nicht unterbrochen werden | Seien Sie bereit, Aufräumen, Signaturprüfung und Rollback bei Fehlschlag selbst zu bauen. Das Hinzufügen von RmGetList/RmShutdown zur Behandlung von „jemand hält es“ macht es solide3 |
| ClickOnce | Interne Windows-Clients, bei denen Sie nur „automatisches Update beim Start“ möchten | Bei einem Update-beim-Start-Modell tritt das Problem der laufenden Verwendung kaum auf. Zu den Einschränkungen siehe „Was ist ClickOnce?“ |
| Selbstupdate auf Dienstseite (separater Update-Prozess) | Unbeaufsichtigte Umgebungen und Geräte-PCs, bei denen ein rund um die Uhr laufender Dienst während des Updates nicht am Laufen gehalten werden kann | Ein Dienst kann sich nicht selbst austauschen. Ein separater Prozess für Stoppen → Austauschen → Starten ist zwingend erforderlich. Achten Sie auf die Sitzungsgrenze (LocalSystem kann Benutzeranwendungen nicht schließen)2 |
Wenn Sie unentschlossen sind, schauen Sie zuerst auf „MSI + Restart-Manager-Integration“. Es baut auf einem Standardmechanismus des Betriebssystems auf, sodass der Implementierungsaufwand minimal ist, und das Dreiteilige-Paket der Anwendung lässt sich unverändert übernehmen, falls Sie später zu einem selbstgebauten Updater wechseln.
8. Zusammenfassung
- Eine verwendete exe oder DLL lässt sich nicht überschreiben oder löschen, kann aber innerhalb desselben Datenträgers umbenannt werden. Umbenennen-dann-Ersetzen, das diese Asymmetrie nutzt, ist die Grundform des „Updates ohne irgendetwas anzuhalten“.
- Restart Manager ist eine Standard-API des Betriebssystems, die das Auflisten der Halter einer Datei (RmGetList), das höfliche Beenden (RmShutdown) und den Neustart nach dem Update (RmRestart) übernimmt, und MSI-Pakete ab Windows Installer 4.0 verwenden sie automatisch.
- Das Verhalten der Anwendung ist ein Dreiteiliges-Paket: mit RegisterApplicationRestart für den Neustart registrieren, mit TRUE auf WM_QUERYENDSESSION (ENDSESSION_CLOSEAPP) antworten und ungesicherte Daten in WM_ENDSESSION speichern, bevor man sich beendet. Allein das ergibt „eine App, die nach einem Update wieder in ihrem ursprünglichen Zustand hochfährt“.
- Die 60-Sekunden-Regel (kein Neustart unmittelbar nach dem Start), die Timeouts für erzwungenes Beenden (30 Sekunden für Anwendungen, 20 für Dienste) und die Sitzungsgrenze (LocalSystem kann Benutzeranwendungen nicht schließen) sind das, worüber man im Produktivbetrieb stolpert.
- Ist ein Austausch wirklich unmöglich, planen Sie ihn mit MoveFileEx + MOVEFILE_DELAY_UNTIL_REBOOT für den nächsten OS-Neustart. Administratorrechte sind zwingend erforderlich, es ist auf denselben Datenträger beschränkt, und Erfolg bedeutet, dass die Reservierung gelungen ist.
- Einen eigenen Update-Mechanismus zu bauen bedeutet, einen eigenen „Mechanismus zur Verteilung beliebiger exe-Dateien“ zu bauen. Entwerfen Sie ihn durchgängig, einschließlich Signaturprüfung, Pfadschutz und Rollback.
Verwandte Artikel
- Eine Windows-App-Verteilungsmethode wählen - MSI/MSIX/ClickOnce/xcopy/eigener Updater
- Sicherheitsdesign für automatische Updates - Warum HTTPS allein nicht genügt
- Hängern und Lecks mit Process Explorer, Handle und VMMap nachjagen
- Win32-APIs sicher aus C# aufrufen — Ein praktischer P/Invoke-Leitfaden (DllImport / LibraryImport / CsWin32)
- Mehrfache Instanzen einer Windows-App verhindern — Benannte Mutexe und die Aktivierung des vorhandenen Fensters beim zweiten Start
- Wie man Windows-Dienste baut und betreibt — Von der Wahl zwischen Aufgabenplanung und Diensten bis zur Umwandlung eines BackgroundService in einen Windows-Dienst
Verwandte Beratungsbereiche
KomuraSoft LLC übernimmt Design und Implementierung von automatischen Update-Mechanismen für Geschäftsanwendungen (Restart-Manager-Integration, selbstgebaute Updater, die Einführung von Squirrel/Velopack), die Verbesserung von Abläufen, bei denen „bei jedem Update alle gebeten werden müssen, die App zu schließen“, sowie die Untersuchung und Behebung von Problemen wie „Datei wird verwendet“ und „ein Neustart ist erforderlich“ in bestehenden Installern.
- Entwicklung von Windows-Apps
- Modernisierung und Wartung von Windows-Apps
- Technische Beratung und Design-Review
- Kontakt
Referenzlinks
</content>
-
Microsoft Learn, About Restart Manager. Zum Zweck von Restart Manager (Neustarts durch Dateien in Verwendung zu verringern oder zu vermeiden); zum Beenden in der Reihenfolge GUI-Apps → Konsolen-Apps → Dienste → Explorer und zum Neustart in umgekehrter Reihenfolge; dazu, dass ein Beenden über Sitzungsgrenzen hinweg nicht unterstützt wird; dazu, dass Windows Installer 4.0 Restart Manager automatisch verwendet; sowie dazu, dass kritische Systemdienste ohne OS-Neustart nicht gestoppt werden können. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, RmShutdown function. Dazu, dass nicht reagierende Anwendungen selbst bei Angabe von RmForceShutdown nach 30 Sekunden (20 Sekunden bei Diensten) zwangsweise beendet werden; dazu, dass RmShutdownOnlyRegistered erlaubt, „nur zu beenden, wenn sich alle für den Neustart registriert haben“; dazu, dass ein LocalSystem-Dienst Anwendungen in anderen Benutzersitzungen weder beenden noch neu starten kann; sowie zu Rückgabewerten wie ERROR_FAIL_NOACTION_REBOOT. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, RmGetList function. Dazu, dass die Anwendungen und Dienste, die die registrierten Ressourcen verwenden, als Array von RM_PROCESS_INFO zurückgegeben werden; zur Aufrufkonvention, bei zu kleinem Puffer ERROR_MORE_DATA (234) samt der benötigten Anzahl zurückzugeben; dazu, dass lpdwRebootReasons den Grund zurückgibt, warum ein OS-Neustart erforderlich ist (RM_REBOOT_REASON); sowie dazu, dass es von rstrtmgr.dll ab Windows Vista bereitgestellt wird. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Using Windows Installer with Restart Manager. Dazu, dass Windows Installer ab 4.0 Restart Manager automatisch verwendet und standardmäßig das Beenden und Neustarten von Anwendungen gegenüber einem OS-Neustart bevorzugt; dazu, dass der Dialog MsiRMFilesInUse bei einer Installation mit vollständiger UI die Option zum automatischen Schließen und Neustarten anbietet (mit Rückfall auf FilesInUse in älteren Umgebungen); dazu, dass bei einer stillen Installation immer Restart Manager verwendet und Anwendungen beendet werden; dazu, dass Custom Actions vor InstallValidate eingeordnet werden müssen und über die Eigenschaft MsiRestartManagerSessionKey RmJoinSession aufrufen sollten, statt selbst RmShutdown und Ähnliches aufzurufen; sowie zu Steuerungseigenschaften wie MSIRESTARTMANAGERCONTROL. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Guidelines for Applications (Restart Manager). Dazu, dass WM_QUERYENDSESSION (lParam=ENDSESSION_CLOSEAPP) an GUI-Anwendungen gesendet wird, die TRUE zurückgeben sollten, wenn sie bereit sind, ohne sich an dieser Stelle zu beenden; dazu, dass das eigentliche Beenden bei WM_ENDSESSION erfolgt; dazu, dass nicht reagierenden Anwendungen zusätzlich WM_CLOSE gesendet wird; dazu, dass Konsolenanwendungen CTRL_C_EVENT erhalten; sowie dazu, dass die Registrierung über RegisterApplicationRestart für einen Neustart zwingend erforderlich ist. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, RegisterApplicationRestart function. Zur Registrierung der Befehlszeile für den Neustart (ohne den Namen der exe, bis zu RESTART_MAX_CMD_LINE); zu den Flags RESTART_NO_CRASH/NO_HANG/NO_PATCH/NO_REBOOT; dazu, dass durch ein Update verursachte Neustarts automatisch erfolgen, während Abstürze und Aufhängen die Zustimmung des Benutzers erfordern; dazu, dass eine Anwendung erst neu gestartet wird, wenn seit dem Start mindestens 60 Sekunden vergangen sind, um Schleifen zu vermeiden; sowie dazu, dass die letzte Gelegenheit zur Registrierung während eines Updates in der Behandlung von WM_QUERYENDSESSION liegt. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, MoveFileExW function. Dazu, dass MOVEFILE_DELAY_UNTIL_REBOOT die Reservierung in PendingFileRenameOperations (REG_MULTI_SZ) unter HKLM\SYSTEM\CurrentControlSet\Control\Session Manager schreibt und diese in Registrierungsreihenfolge nach dem Lauf von AUTOCHK und vor dem Anlegen der Auslagerungsdatei ausgeführt wird; zur Anforderung von Mitgliedschaft in der Administratorengruppe oder LocalSystem-Rechten; dazu, dass es sich nicht mit MOVEFILE_COPY_ALLOWED kombinieren lässt; dazu, dass MOVEFILE_REPLACE_EXISTING erforderlich ist, wenn die Zieldatei bereits existiert; dazu, dass der Rückgabewert den Erfolg der Reservierung anzeigt, nicht des tatsächlichen Verschiebens; sowie dazu, dass die Übergabe von NULL für lpNewFileName zum Löschen beim Neustart führt. ↩ ↩2 ↩3
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Laufen Business-Anwendungen unter Windows on Arm? ── Die Realität von x64-Emulation (Prism) und nativen DLLs/COM
Eine Antwort für Entwicklerinnen, Entwickler und IT-Verantwortliche auf die Frage „Läuft unsere Business-Anwendung unter Windows on Arm?“...
Win32-APIs sicher aus C# aufrufen — Ein praxistauglicher P/Invoke-Leitfaden (DllImport / LibraryImport / CsWin32)
Eine praxisnahe Übersicht darüber, worauf beim Aufruf von Win32-APIs und nativen DLLs aus C# über P/Invoke zu achten ist. Behandelt die U...
Was ist das TPM in Windows? — Eine bebilderte Einführung in den „Tresor, der Schlüssel nie herausgibt“ und das gemessene Starten
Eine bebilderte Erklärung des TPM: wie Schlüssel den Chip nie verlassen, PCRs und Measured Boot, wie BitLocker und Windows Hello es nutze...
Verwenden Sie den ausgelesenen Wert eines QR-Codes nie ungeprüft — Erfolgreiche Fehlerkorrektur garantiert nicht den Wert
Die Fehlerkorrektur von QR-Codes ist kein Mechanismus, der garantiert, dass der Wert korrekt ist, sobald die Korrektur gelingt. Anhand vo...
USB-Geräte aus einer Windows-App ansprechen — Die Wahl zwischen virtual COM, HID, WinUSB und Hersteller-SDKs
Ein Vergleich von vier Wegen, Geräte und USB-Hardware aus einer Windows-Anwendung anzusteuern: virtueller COM-Port, HID, WinUSB und Herst...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Warum lässt sich eine laufende exe oder eine geladene DLL umbenennen, aber nicht überschreiben?
- Windows hält eine laufende exe oder eine geladene DLL als Speicherabbildung (einen Image-Abschnitt), sodass eine Änderung des Dateiinhalts oder das Löschen mit einem Fehler fehlschlägt (eine Freigabeverletzung oder Zugriff verweigert). Der „Name“ im Verzeichnis ist jedoch unabhängig vom Dateiinhalt, sodass ein Umbenennen (Verschieben) innerhalb desselben Datenträgers auch bei laufender Datei gelingt. Das Muster Umbenennen-dann-Ersetzen nutzt diese Asymmetrie aus: Die alte exe wird auf einen Ausmusterungsnamen umbenannt, die neue exe wird unter dem ursprünglichen Namen platziert, und ab dem nächsten Start wird die neue Version verwendet. Auch das automatische Update von Chrome sowie Frameworks wie Squirrel und Velopack bauen im Kern auf dieser Eigenschaft auf.
- Was tut die Restart-Manager-API konkret für mich?
- Sie registrieren die Dateien, die Sie aktualisieren möchten, und die API listet auf, „welche Anwendungen und Dienste diese Dateien gerade verwenden“, beendet sie nach Möglichkeit und kann sie nach dem Update sogar wieder starten. Sie ist eine Standard-Windows-API (ab Windows Vista). Der Ablauf lautet: mit RmStartSession eine Sitzung erstellen, mit RmRegisterResources die Zieldateien registrieren, mit RmGetList die haltenden Prozesse auflisten, mit RmShutdown beenden, mit RmRestart neu starten und zuletzt RmEndSession aufrufen. Das Beenden erfolgt in der Reihenfolge GUI-Apps → Konsolen-Apps → Dienste → Explorer, der Neustart in umgekehrter Reihenfolge. Sie existiert, um „ein Neustart ist erforderlich“-Meldungen zu verringern, und MSI-Pakete ab Windows Installer 4.0 nutzen sie automatisch.
- Was passiert, wenn ich RegisterApplicationRestart aufrufe?
- Ihre App registriert ihre eigene Neustart-Befehlszeile beim Betriebssystem, und nach einem durch ein Update verursachten Beenden startet Restart Manager die App mit dieser Befehlszeile neu (Restart Manager kann nur registrierte Apps neu starten). Die App wird außerdem für einen Neustart nach einem Absturz oder Aufhängen infrage kommen, wobei in diesen Fällen die Zustimmung des Benutzers eingeholt wird; durch ein Update verursachte Neustarts erfolgen automatisch. Beachten Sie, dass eine App zur Vermeidung von Schleifen erst neu gestartet wird, wenn seit ihrem Start mindestens 60 Sekunden vergangen sind, und dass die Befehlszeile den Namen der exe nicht enthalten darf. Gute Praxis ist es, sich mit allen für die Wiederherstellung nötigen Zustandsdaten (etwa, welche Datei geöffnet war) in die Befehlszeilenargumente eingebettet erneut zu registrieren.
- Wann verwendet man MoveFileEx mit MOVEFILE_DELAY_UNTIL_REBOOT?
- Es ist die letzte Möglichkeit, wenn Sie den Prozess einfach nicht stoppen können und Umbenennen-dann-Ersetzen ebenfalls nicht verfügbar ist: Es plant ein Verschieben oder Löschen einer Datei für den nächsten Systemneustart. Die Reservierung wird unter PendingFileRenameOperations in der Registrierung abgelegt (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager) und in Registrierungsreihenfolge früh im nächsten Startvorgang ausgeführt (nach AUTOCHK, vor dem Anlegen der Auslagerungsdatei). Der Aufruf setzt Mitgliedschaft in der Administratorengruppe oder LocalSystem-Rechte voraus und lässt sich nicht mit MOVEFILE_COPY_ALLOWED kombinieren, kann also nicht zum Verschieben über Datenträgergrenzen hinweg verwendet werden. Ebenfalls wichtig: Erfolg oder Misserfolg der Funktion bezieht sich auf die Reservierung, nicht auf den tatsächlichen Austausch.
- Wie behandelt man „Dateien werden verwendet“ elegant in einem MSI-Installer?
- Windows Installer ab 4.0 integriert sich automatisch mit Restart Manager und bevorzugt standardmäßig das Beenden und Neustarten von Anwendungen gegenüber einem Neustart des Betriebssystems. Fügen Sie Ihrem Paket den Dialog MsiRMFilesInUse hinzu, wird Benutzern bei einer Installation mit vollständiger UI die Option angeboten, „Anwendungen automatisch zu schließen und neu zu starten“. Bei Ausführung unter einem älteren Windows Installer fällt es auf den herkömmlichen FilesInUse-Dialog zurück, weshalb es üblich ist, beide einzubinden. Das Verhalten lässt sich mit Eigenschaften wie MSIRESTARTMANAGERCONTROL steuern, und bei einer stillen Installation wird Restart Manager immer verwendet und Anwendungen werden automatisch beendet. Implementiert die Anwendung RegisterApplicationRestart und reagiert auf WM_QUERYENDSESSION, erhalten Sie ein Update-Erlebnis, das nahezu nahtlos wirkt, wobei die App danach wieder in ihrem ursprünglichen Zustand hochfährt.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.