Windows Shutdown from the App's Point of View — Surviving Exit Notifications, Restarts, and Power Loss Correctly
· Updated: · Go Komura · Windows, Shutdown, Windows Development, Windows Service, Device PC, Data Integrity, Long-Running Operation, UPS
Revision history (1 updates, last updated Sep 8, 2026)
A log of the changes made to this article. Where a pre-update version was archived, it stays readable at a permanent DOI link.
- Retranslated as a full translation of the current Japanese original. The previous English version was an abridgement that dropped subsections, tables, diagrams, and paragraphs; all of them have been restored to match the Japanese article, and the knowledge map section has been added where the Japanese article has one. The technical claims are the same as in the Japanese version. Read the version before this update (DOI: 10.5281/zenodo.22170887)
- First published
Cite this article(DOI: 10.5281/zenodo.22170886)
This article is archived on Zenodo. Below are both the DOI that always resolves to the latest version and the DOI pinned to the version you are reading.
Go Komura (2026). Windows Shutdown from the App's Point of View — Surviving Exit Notifications, Restarts, and Power Loss Correctly. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22170886 https://comcomponent.com/en/blog/windows-shutdown-handling-for-apps/
- DOI (latest version)
- 10.5281/zenodo.22170886
- DOI (this version)
- 10.5281/zenodo.22652537
“A nightly Windows Update restart corrupted the file we were measuring into.” “Signing out of a shared PC wipes what I was editing.” To prevent this kind of breakage, shutdown must not be treated as an exception: being asked from outside to exit has to be designed as normal behavior of the app.
Code that receives the exit notification is not enough on its own, though. The time available after the notification is short, and a sudden power loss brings no notification at all. The central idea of this article is to make routine saving, a short cleanup, and recovery at the next startup one continuous whole.
For IT staff at small and medium-sized businesses and for Windows app developers, especially those who work with device PCs and long-running apps, this article walks through choosing a notification path, implementing it, recovering after a restart, preparing for power loss, and verifying the result. The technical basis is the Microsoft Learn primary sources, as of August 2026, that the original article referenced.
1. The Bottom Line First — Reduce What Must Be Saved Before Waiting for a Notification
Keep the app in a state where it can exit within a few seconds of a notification, and make sure the next startup can recover even when no notification arrives. That is the basic policy for shutdown handling.
Rather than saving a large amount of data after the exit notification, save at each milestone of the work and keep the remaining delta at exit small. For GUI apps and console close, about 5 seconds is the key figure. Services have a different grace period, but none of these is time you are guaranteed to get in full.123
1.1. Choose the Notification Path for Your App
| App type | Where the exit request arrives | First thing to get right | Section to read |
|---|---|---|---|
| Win32 GUI app | WM_QUERYENDSESSION and WM_ENDSESSION |
Answer the query with TRUE immediately, as a rule. Do the cleanup after the exit is committed in WM_ENDSESSION |
Section 3 |
| WinForms / WPF | FormClosing / SessionEnding, plus a message hook where needed |
Both events belong to the query phase. Move cleanup that cannot be undone to the committed notification | Section 3 |
| Plain console app | SetConsoleCtrlHandler and similar |
Close and sign-out/shutdown are notified under different conditions | Section 5 |
| Windows service | SHUTDOWN / PRESHUTDOWN from the SCM |
Declare the accepted-controls flag and return from the control handler right away | Section 6 |
| Generic Host / Worker Service | IHostApplicationLifetime and StopAsync |
Concentrate cleanup on the Host’s stop path and set ShutdownTimeout explicitly |
Sections 5 and 6 |
In .NET, do not rely on AppDomain.ProcessExit alone. From .NET 10 onward, the runtime provides no default handler for CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT. This external-signal path is separate from a normal exit such as returning from Main. Section 5.2 covers the details.4
1.2. Two Preparations Are Needed Outside the Notification Handling
When the notification arrives, do not ask the user “Do you want to save?”; finish with a short cleanup and exit. Temporarily blocking for work that truly cannot be interrupted is covered in Section 4, and automatic recovery after a restart in Section 7.
The other preparation is leaving data that can still be read after a power loss that brings no notification. Saving to a temporary file, flushing, swapping, keeping a backup, and validating at startup are covered together in Section 8. Once the notification path is implemented, go on to check the save and recovery design in Section 8 and the verification in Section 9.
flowchart TB
accTitle: A save design shared by exit notifications and power loss
accDescr: Routine saving keeps the unsaved delta small; with a notification a short cleanup follows, and without one the saved data is validated and recovered before work resumes
daily["Save at each milestone"] --> event{"Is there a notification at exit?"}
event -->|"Yes"| close["Save only the remaining delta and exit"]
event -->|"No"| lost["No cleanup is possible on the spot"]
close --> boot["Validate and recover at the next startup"]
lost --> boot
boot --> resume["Resume work"]
Figure 1: Rather than working hard only when notified, make routine saving through the next startup one continuous process.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (14 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. Distinguish First — Sign-Out, Shutdown, Restart, and Power Loss
2.1. Look at the User Session and the Kernel Separately
Splitting “what ends” into two parts makes the handling easier to organize. The user session is the scope in which that user’s apps run. The kernel and drivers, on the other hand, belong to the OS side and keep running after the user signs out.
| Operation | User session | Kernel and drivers | Notifications |
|---|---|---|---|
| Sign-out | Ends | Keeps running | The GUI gets the exit query and the committed notification. Services do not stop on sign-out |
| Shutdown with fast startup enabled | Ends | Saves its hibernation state to hiberfil.sys |
The GUI’s session-ending notifications and the shutdown notification to services |
| Restart | Ends | Ends completely and does a full boot next time | Same as above |
| Sudden power loss | Lost instantly | Lost instantly | No notification |
In the GUI’s WM_QUERYENDSESSION, the ENDSESSION_LOGOFF bit in lParam indicates a sign-out. If lParam is 0 it is a shutdown or a restart, and the two cannot be told apart. Treat lParam as a bit mask.1
For the app’s unsaved data, sign-out and shutdown call for the same preparation. Do not split them into “no need to save on sign-out”; route both into a common save routine. That does not make the notification conditions for console apps and services identical, though; check the paths in Sections 5 and 6 for each.
flowchart TB
accTitle: Think of sign-out and system exit separately
accDescr: Sign-out ends the user's apps while services keep running; a system shutdown or restart also stops services; a power loss notifies neither
signout["Sign-out"] --> user["The user's apps exit"]
signout -.-> alive["Services keep running"]
system["Shutdown or restart"] --> user
system --> svc["Services are stopped too"]
power["Power loss"] --> none["No notification to either"]
Figure 2: A sign-out lets you exercise the GUI’s exit handling, but it does not count as testing a service’s stop handling.
2.2. Why “Shutdown Does Not Fix It, but Restart Does”
On client OSes from Windows 8 onward, fast startup is enabled by default on many PCs that support hibernation. In this configuration a shutdown signs the user out, but the state of the kernel and device drivers is saved to the hibernation file and restored at the next boot. Cutting the power does not necessarily mean the whole OS state was reset.5
This behavior is conditional. Where hibernation is disabled (powercfg /hibernate off), where fast startup is turned off by policy or in Power Options, and on Windows Server, you get the traditional full shutdown. Check the Power Options settings, and use powercfg /a to check whether fast startup is available.
“Restart”, by contrast, always performs a full boot cycle. In a procedure for isolating driver trouble, write “restart” explicitly rather than “turn it off and on again”.5
flowchart TB
accTitle: Fast startup versus a full boot
accDescr: A shutdown with fast startup enabled saves and restores the kernel and driver state, while a full shutdown or a restart initializes everything with a full boot
s["Shutdown"] --> q{"Use fast startup?"}
q -->|"Yes"| save["Hibernate the kernel and drivers"]
save --> restore["Restore the state at the next startup"]
q -->|"No"| full["Full shutdown"]
full --> boot["Initialize with a full boot next time"]
r["Restart"] --> boot
Figure 3: Cutting the power is not the same as resetting the kernel and drivers.
From the command line, shutdown /s requests an explicit full shutdown and shutdown /s /hybrid the hybrid behavior. Shutdown.exe defaults to a full shutdown, so it is important not to assume it is the same as the “Shut down” item on screen.5
Disabling fast startup as a workaround is not recommended. Build the app to work whether it is enabled or disabled. For example, do not estimate a device’s “cumulative operating time” from the OS boot time alone; account in the design for the fact that kernel state can be carried over.
3. GUI Apps — Separate the Query from the Committed Exit
3.1. WM_QUERYENDSESSION Is the Question, WM_ENDSESSION Is the Result
An app that has a window and a message queue is notified of the end of a session in two stages.1
| Message | Meaning | What to do |
|---|---|---|
WM_QUERYENDSESSION |
The query “Is it OK to exit?” | Return TRUE immediately, as a rule. DefWindowProc also defaults to TRUE |
WM_ENDSESSION, wParam=TRUE |
The end of the session is committed | Do the short cleanup: save, disconnect, and so on |
WM_ENDSESSION, wParam=FALSE |
The end of the session was canceled | The app keeps running. Do not do cleanup that is only possible after the exit is committed |
Returning TRUE to the query yourself does not mean the exit is certain. Another app may refuse, and the exit is canceled. If you disconnect or hand over resources you need at this point, an app that did not exit can no longer work. That is why cleanup is moved to the committed notification.1
flowchart TB
accTitle: The GUI's query and the exit result
accDescr: Even after returning TRUE to WM_QUERYENDSESSION another app can cancel the exit, so post-commit cleanup runs only when WM_ENDSESSION's wParam is TRUE
query["WM_QUERYENDSESSION"] --> reply["Return TRUE immediately, as a rule"]
reply --> result["WM_ENDSESSION"]
result --> yes{"Is wParam TRUE?"}
yes -->|"Yes"| cleanup["Post-commit cleanup"]
yes -->|"No"| running["Exit canceled; keep running"]
Figure 4: Do not confuse the reply that permits the exit with the notification that the exit is committed.
There are cases where you can return FALSE to refuse the exit, but the rule is to respect the user’s intent to exit. An app that refuses is displayed as an app that is preventing shutdown. Console apps and apps without a visible window have constraints as well: in an ordinary configuration, an app that does not respond within 5 seconds can be terminated automatically. Treat blocking as the exceptional handling of Section 4 and do not use it for ordinary saving.6
3.2. About 5 Seconds Is Not a Guarantee That You Can Finish Saving
If you delay the response by about 5 seconds at either the WM_QUERYENDSESSION or the WM_ENDSESSION stage, the system shows the screen listing the apps that are preventing shutdown, and the user can choose to force the shutdown. After a forced termination there is no opportunity to finish the rest of the save.6
The countermeasure is to save routinely and reduce the delta at exit. Park unsaved working state in a temporary location and restore it at the next startup. Do not design the app to show a confirmation dialog during shutdown and wait. Treat the ordinary exit confirmation and an exit request from the OS as separate things.1
flowchart TB
accTitle: Keep the work at exit small
accDescr: A design that saves routinely leaves a small delta at exit, whereas a design that accumulates in memory until exit does not fit in the short grace period and risks loss through forced termination
good["Save routinely"] --> small["The delta left at exit is small"]
small --> fast["Exit with a short cleanup"]
bad["Accumulate in memory until exit"] --> large["Save everything at exit"]
large --> risk["Too little grace period; risk of forced termination"]
Figure 5: The preparation for exiting in a few seconds happens before the exit notification.
3.3. In WinForms and WPF, Separate Saving from Cleanup That Cannot Be Undone
In WinForms the counterpart is FormClosing with CloseReason.WindowsShutDown; in WPF it is Application.SessionEnding. WPF can also register it through the SessionEnding attribute in XAML, and it can be handled by overriding OnSessionEnding.
Both, however, are query-phase events. What is allowed here is at most an “idempotent snapshot save”: harmless if the exit is canceled, and producing the same result no matter how many times it runs. Work that is only possible after the exit is committed, such as disconnecting, is done by receiving WM_ENDSESSION with wParam=TRUE in WinForms’ WndProc or a WPF hook.
flowchart TB
accTitle: Division of exit handling in WinForms and WPF
accDescr: FormClosing and SessionEnding save a snapshot that is safe even if the exit is canceled, and a hook on WM_ENDSESSION with TRUE performs the cleanup that cannot be undone
notify["Query phase"] --> forms["FormClosing"]
notify --> wpf["SessionEnding"]
forms --> snapshot["Idempotent snapshot save"]
wpf --> snapshot
final["WM_ENDSESSION with TRUE"] --> hook["Receive in WndProc or a hook"]
hook --> cleanup["Post-commit work such as disconnecting"]
Figure 6: Do not cram post-commit work into the framework events.
The next two examples are the part that saves only the working state at the query stage. The code samples are implementation excerpts; the contents of the save function, failure handling, event registration, and so on are up to the app.
// WinForms: FormClosing is also called on shutdown and sign-out
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.WindowsShutDown)
{
// Do only an idempotent snapshot save. Show no dialog.
// Do not set e.Cancel = true (refuse) either.
SaveWorkingStateToTempFile();
return;
}
// In normal cases, such as the user closing with the X button, confirming here is fine
}
// WPF: App.xaml.cs
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
base.OnSessionEnding(e);
// ReasonSessionEnding.Logoff / Shutdown can be told apart, but
// the basic approach is to run the same snapshot save for both
SaveWorkingStateToTempFile();
// Do not set e.Cancel = true unless there is a very good reason
}
In both cases, route the data used on normal exit, on shutdown, and, if possible, on crash into a common save function and format. If you avoid creating a separate format for each save path, the restore logic at the next startup can also be a single one. Designing to leave information behind on a crash is covered in Designing Windows Apps to Leave Logs and Dumps on a Crash.
4. Block Temporarily, and Only for Work That Cannot Be Interrupted
4.1. Registering a Reason and Refusing to Exit Are Separate Jobs
Work that is physically destroyed if interrupted, such as burning a CD or writing firmware, is the exception. Register a reason with ShutdownBlockReasonCreate when the work starts, and clear it with ShutdownBlockReasonDestroy when it finishes. The registered reason is shown on the screen listing the apps that are preventing shutdown.7
Note, however, that registering a reason string alone does not stop the shutdown. Combine it with a “protected” flag and, only while it is set, return FALSE to WM_QUERYENDSESSION. When the work finishes, clear both the reason and the protected flag.
flowchart TB
accTitle: Division of roles in a temporary exit block
accDescr: Only while uninterruptible work runs, combine reason registration with refusing the query and clear both on completion; a forced shutdown by the user still cannot be prevented
start["Uninterruptible work starts"] --> reason["Register the reason and set the protected flag"]
reason --> work["Process on a worker"]
work --> done["Finish; clear the reason and the flag"]
work -.-> request["Exit query arrives meanwhile"]
request --> refuse["Return FALSE and show the reason"]
refuse --> choice{"User's decision"}
choice -->|"Cancel"| keep["Keep running"]
choice -->|"Force"| terminate["Can be terminated"]
Figure 7: Registering a reason alone is not a refusal, and even implementing the refusal does not prevent a forced shutdown.
4.2. Keep the UI Thread Able to Receive the Exit Request
Register and clear the reason from the thread that created the target window. Calls from other threads fail.7 The long uninterruptible work itself, on the other hand, moves to a worker thread. If the UI thread is blocked by synchronous processing, the app becomes “Not responding” before the message needed to refuse is ever processed.
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, string pwszReason);
[DllImport("user32.dll", SetLastError = true)]
static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);
// Call from the thread that created the main window (calls from other threads fail)
_criticalOperationInProgress = true;
ShutdownBlockReasonCreate(this.Handle, "Writing measurement data to a file");
try
{
// Run the uninterruptible work on a worker thread. Running it synchronously on the
// UI thread stops the message pump, and the app is force-continued as
// "Not responding" before the WM_QUERYENDSESSION refusal code below can run
await Task.Run(() => WriteMeasurementData());
}
finally
{
ShutdownBlockReasonDestroy(this.Handle);
_criticalOperationInProgress = false;
}
// In addition, return FALSE to WM_QUERYENDSESSION only while protected, to refuse
protected override void WndProc(ref Message m)
{
const int WM_QUERYENDSESSION = 0x0011;
if (m.Msg == WM_QUERYENDSESSION && _criticalOperationInProgress)
{
m.Result = IntPtr.Zero; // Refuse. The registered reason string is shown in the full-screen UI
return;
}
base.WndProc(ref m);
}
This example is the skeleton that combines registering the reason, processing on a worker, and refusing the query. Error handling, including API failures, is needed separately. Keep the reason string short and concrete, and limit the registration to the time the work truly cannot be interrupted rather than keeping it registered for the whole life of the app.7
Even so, the user can choose to force the shutdown. There are forced-exit paths such as ENDSESSION_CRITICAL as well, so never make being able to block a premise of data integrity. The preparation for the case where you could not stop it is the save and recovery design of Section 8.6
5. Console Apps and .NET — Check the Conditions Under Which Notifications Arrive
5.1. Notifications and Grace Periods Received Through SetConsoleCtrlHandler
In a console app, control signals arrive at the handler registered with SetConsoleCtrlHandler. Unlike GUI message handling, the handler runs on a separate thread.2
| Signal | When it occurs | Default grace period |
|---|---|---|
CTRL_C_EVENT / CTRL_BREAK_EVENT |
Ctrl+C / Ctrl+Break | No explicit timeout |
CTRL_CLOSE_EVENT |
Closing the console, “End task” in Task Manager, and so on | About 5 seconds |
CTRL_SHUTDOWN_EVENT |
Service processes at system shutdown | About 20 seconds |
Forcibly terminating a process from the “Details” tab of Task Manager and the like is an immediate termination without notification, and is outside this table.2
Do not design a console app in an interactive session to wait for CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT. Interactive apps are terminated at the moment of sign-out, so in practice only processes running as services can receive these. Furthermore, a process that loads gdi32.dll or user32.dll is treated as a Windows app, and the LOGOFF/SHUTDOWN handlers are not called. The official workaround in that case is to create a hidden window and receive WM_QUERYENDSESSION / WM_ENDSESSION.28
flowchart TB
accTitle: Conditions for receiving console exit notifications
accDescr: An interactive console can receive the close notification but cannot expect logoff or shutdown notifications, and even a service needs a different notification path once it loads the GUI DLLs
console["Console process"] --> close["Close goes to the control handler"]
console --> q{"Waiting for LOGOFF or SHUTDOWN?"}
q -->|"Interactive session"| no["Cannot rely on this notification"]
q -->|"Service"| dll{"Loaded the GUI DLLs?"}
dll -->|"No"| signal["Handle the relevant control signal"]
dll -->|"Yes"| window["Receive through a hidden window"]
Figure 8: Do not treat handling of console close as shutdown handling.
The next excerpt prepares for Ctrl+C and console close. It holds on to the delegate to keep the GC from collecting it, and after a short cleanup proceeds to the default handler. Registering this alone does not guarantee shutdown notifications for an interactive app.
// Console app: clean up on Ctrl+C and console close
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);
delegate bool HandlerRoutine(int ctrlType); // 2 = CTRL_CLOSE_EVENT
static readonly HandlerRoutine s_handler = OnCtrlEvent; // Keep a reference so the GC does not collect it
static bool OnCtrlEvent(int ctrlType)
{
// Do only cleanup that finishes within 5 seconds
FlushAndCloseDataFile();
return false; // Proceed to the default handler; the process exits
}
static void Main()
{
SetConsoleCtrlHandler(s_handler, add: true);
// ...
}
5.2. From .NET 10 Onward, Do Not Put Cleanup Only in ProcessExit
From .NET 10, the runtime no longer provides a default handler for CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT. Without a handler of your own or from a higher-level library, the OS default processing terminates the process, and on this path neither AppDomain.ProcessExit nor AssemblyLoadContext.Unloading fires.4
This is a change to the default behavior for external termination signals. It does not mean that ProcessExit no longer fires on a normal exit such as returning from Main.
flowchart TB
accTitle: Removing the dependency on .NET's default termination handler
accDescr: The older runtime connected the relevant signals to ProcessExit, but from .NET 10 no default handler is provided, so use the notification path of the app model
signal["CLOSE or SHUTDOWN signal"] --> old["Older runtime's default handling"]
old --> event["Raises ProcessExit and the like"]
signal --> modern["No default handling from .NET 10 on"]
modern --> own{"Does the app handle it?"}
own -->|"No"| os["Terminated by OS default handling"]
own -->|"Yes"| handle["Cleanup that fits the model"]
Figure 9: Distinguish a normal exit from termination by an external signal, and provide the handler your app model needs.
Concentrate cleanup where it fits the app model. For a GUI, that is the events and the committed notification of Section 3; for Generic Host, IHostApplicationLifetime and BackgroundService.StopAsync; for a plain console app, SetConsoleCtrlHandler or PosixSignalRegistration. When using the latter, choose the signals that correspond to the termination paths you target, such as SIGINT, SIGTERM, and SIGHUP.4
In Generic Host, set HostOptions.ShutdownTimeout explicitly. The Host-side setting alone, however, does not extend the outer grace period of the GUI, the console, or the SCM. Even though Ctrl+C has no explicit timeout, you still need to prepare for power loss and other termination paths. In every model the policy is the same: keep the state saved and keep the work after the notification small.
flowchart TB
accTitle: The Host's stop processing and the outer exit grace period
accDescr: Generic Host's stop is concentrated in StopAsync, but setting the HostOptions timeout does not extend the OS or SCM exit grace period, so check both
outer["Exit request from the OS or SCM"] --> host["The Host's stop path"]
host --> stop["Clean up in StopAsync"]
stop --> inner["Set the Host-side stop time"]
outer -.-> limit["The outer grace period exists separately"]
inner --> check["Measure whether it finishes quickly"]
limit --> check
Figure 10: Check the Host-side and OS-side time limits separately, and do not settle for extending the setting.
6. Windows Services — Return from the Control Handler Immediately
6.1. The Difference Between SHUTDOWN and PRESHUTDOWN
Services do not stop on sign-out, but they are stopped on shutdown and restart. The notification comes from the SCM (Service Control Manager), and the service must declare the flag that corresponds to the control code it wants to receive.3
| Flag to declare | Control code delivered | When to use it |
|---|---|---|
SERVICE_ACCEPT_SHUTDOWN |
SERVICE_CONTROL_SHUTDOWN |
The ordinary shutdown notification. The default grace period is about 20 seconds and depends on WaitToKillServiceTimeout |
SERVICE_ACCEPT_PRESHUTDOWN |
SERVICE_CONTROL_PRESHUTDOWN |
Delivered before the ordinary SHUTDOWN. The SCM waits until the service stops or the configured timeout expires |
flowchart TB
accTitle: Stages of exit notification to services
accDescr: The SCM notifies services that accept PRESHUTDOWN first, waits for them to stop or time out, and then proceeds to the ordinary SHUTDOWN notification
start["System shutdown begins"] --> pre["Notify services accepting PRESHUTDOWN"]
pre --> wait["Wait for stop or the configured deadline"]
wait --> shut["Notify services accepting SHUTDOWN"]
shut --> proceed["After the grace period, proceed to system exit"]
Figure 11: Being notified early and being waited for are separate conditions; PRESHUTDOWN has a configured deadline too.
The PRESHUTDOWN timeout is configured with ChangeServiceConfig2(SERVICE_CONFIG_PRESHUTDOWN_INFO). The default is 10 seconds from Windows 10 Creators Update (build 15063) onward and 3 minutes before that. If you assume “PRESHUTDOWN always buys 3 minutes”, you will not get the time you expected.9
Because PRESHUTDOWN holds up the shutdown of the whole system, use it only when truly necessary. Rewriting the ordinary WaitToKillServiceTimeout from the service side to extend it is not recommended either.3
6.2. Separate Receiving the Notification from Actually Stopping
The control handler must return within 30 seconds, but rather than thinking of that as 30 seconds you may use, structure it to signal the stop and return immediately. Hand the long work to another thread and report SERVICE_STOP_PENDING.3
// Win32 service: accept PRESHUTDOWN and leave the stop work to a worker
g_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PRESHUTDOWN;
DWORD WINAPI ServiceHandlerEx(DWORD control, DWORD, LPVOID, LPVOID)
{
switch (control)
{
case SERVICE_CONTROL_PRESHUTDOWN:
case SERVICE_CONTROL_STOP:
ReportStatus(SERVICE_STOP_PENDING, /*waitHintMs*/ 3000);
SetEvent(g_stopEvent); // Signal the worker to stop and return immediately
return NO_ERROR;
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
// Worker side: if the cleanup takes longer than the wait hint, keep reporting
// SERVICE_STOP_PENDING periodically while incrementing dwCheckPoint. The SCM judges
// from the wait hint and the advancing checkpoint that the service is "still alive and
// making progress". If the reports stop, it is treated as hung and the shutdown can
// move on. Always report SERVICE_STOPPED when finished
On the worker side, if the cleanup takes longer than the wait hint, keep reporting SERVICE_STOP_PENDING while advancing dwCheckPoint. If the progress reports stop, the service can be judged hung. Reporting SERVICE_STOPPED on completion is part of the stop processing.39
flowchart TB
accTitle: Division of work between the service control handler and the worker
accDescr: The control handler reports stop pending, signals the worker, and returns right away; the worker does the cleanup and progress reporting and finally reports stopped
handler["Control handler"] --> pending["Report STOP_PENDING"]
pending --> signal["Signal the worker to stop"]
signal --> back["The handler returns immediately"]
signal --> worker["The worker cleans up"]
worker --> report["Report progress if it takes long"]
report --> stopped["Report STOPPED on completion"]
Figure 12: Do not block the thread that receives the notification with long stop processing.
6.3. Be Able to Finish Quickly Even If a Dependency Stops First
At shutdown, the SCM by default sends notifications without regard to service dependencies. Stop processing must handle the case where a dependency service is already unavailable, and must not wait too long for replies from network peers. Rather than spending time on freeing memory and the like, give priority to committing the necessary data quickly.3
This policy also matters in relation to a UPS. The longer a service extends its wait, the harder it becomes to complete the shutdown of the whole OS before the battery runs out. Instead of increasing the grace period, reduce the work at stop time by saving at each milestone.3
When a .NET Worker Service runs under UseWindowsService, STOP / SHUTDOWN are converted into a Host stop, which leads to BackgroundService.StopAsync. The standard implementation, as of the original article’s writing, does not accept PRESHUTDOWN, so if you need it you have to extend the handler implementation. The policy of setting HostOptions.ShutdownTimeout explicitly and finishing StopAsync itself in a few seconds is the same. For the overall implementation, see How to Build and Operate a Windows Service.
7. Recovery After a Restart — Line Up Registration, Restore Data, and Sign-In
7.1. RegisterApplicationRestart Pre-Registers the Recovery Path
On device PCs and in unattended operation, the design goes beyond saving and exiting: it covers resuming work after the restart. RegisterApplicationRestart is the API that registers the app for restart in each of these situations: a crash (an unhandled exception), not responding, an app restart caused by an update, and an OS restart caused by an update.10
You can specify command-line arguments for the restart, such as the files that were open or a restore point. Registering alone, however, does not mean automatic recovery from every situation.
| Item to check | Condition or constraint |
|---|---|
| When to register | Before a problem occurs. In the update scenario, while handling WM_QUERYENDSESSION is the last opportunity |
| Restart loop prevention | A process that has been running for less than 60 seconds is not restarted |
| Crash or hang | Restarted after the user consents. A restart caused by an update is automatic |
| Spanning an OS restart | The initiating side must call the shutdown API with EWX_RESTARTAPPS / SHUTDOWN_RESTARTAPPS |
| Elevated process | Not eligible for automatic restart, so a separate explicit launch path is needed |
For an app that needs elevation, design the recovery path by running the UI at standard privileges and separating the privileged work into a service, or by using a Task Scheduler task set to “Run with highest privileges” or similar.10
7.2. The Recovery Callback and ARSO Play Different Roles
If you also use RegisterApplicationRecoveryCallback, WER (Windows Error Reporting) calls the recovery callback on a crash and you can save the data being worked on. While the save continues, call ApplicationRecoveryInProgress within the registered ping interval, and notify ApplicationRecoveryFinished on completion. If the progress notifications stop, the recovery processing can be cut off.
flowchart TB
accTitle: Saving and progress notification in the recovery callback
accDescr: The recovery callback invoked by WER keeps calling ApplicationRecoveryInProgress within the ping interval while saving, and notifies ApplicationRecoveryFinished on completion
reg["Pre-register the recovery callback"] --> crash["Crash; WER invokes it"]
crash --> save["Save the data being worked on"]
save --> progress["Report progress within the ping interval"]
progress --> completed{"Save complete?"}
completed -->|"Not yet"| save
completed -->|"Done"| done["Notify recovery finished"]
progress -.-> timeout["Can be cut off if reports stop"]
Figure 13: Do not just save; notify WER of progress and completion.
Replacing files that are in use during an update and restarting is the job of Restart Manager. It is covered in How to Replace an EXE or DLL That Is in Use.
The mechanism that brings the user session back after an OS restart, on the other hand, is ARSO (Winlogon automatic restart sign-on). When Windows Update starts an automatic restart, it securely saves the credentials of the last interactive user and configures Autologon, and after the restart it signs that user in and locks the screen.11
flowchart TB
accTitle: Restart registration, sign-in, and data restoration
accDescr: Against a prior restart registration, a crash requires the user's consent, and user apps after an OS restart need the session restored through ARSO or similar and the saved state restored
reg["Register for restart before a problem occurs"] --> crash["Crash or not responding"]
crash --> consent["Obtain the user's consent"]
consent --> app["Restart the app"]
reg --> update["OS restart with the required flags"]
update --> session["Restore the session through ARSO or similar"]
session --> app
app --> data["Read the saved restore point"]
session -.-> policy["Check the policy and startup conditions"]
Figure 14: Provide not only the app’s restart registration but also the path by which the session and the working state come back.
shutdown /g is the command that requests a restart plus resumption of registered apps. ARSO may be disabled by organizational policy such as DisableAutomaticRestartSignOn, so check it together with the requirements for unattended recovery. Background processing that is always needed is better run as a Windows service than made to depend on the user’s automatic sign-in.11
8. Power Loss Without Notification — Design Saving and Loading as a Pair
8.1. Temporary File, Swap, Backup, and Startup Validation as One Set
A tripped breaker, a failed power supply unit, or a pulled plug brings neither WM_ENDSESSION nor PRESHUTDOWN. If you overwrite the original file in place, an interruption midway can leave a file with old and new contents mixed together.
The baseline is to write everything to a temporary file on the same volume, flush it, and then swap it in. ReplaceFile bundles the steps that correspond to saving to a new file, setting the original aside, renaming, and deleting, and it carries over attributes such as the creation time, the ACL, and alternate data streams. The replaced file, the replacement file, and the backup must be on the same volume. .NET’s File.Replace calls this API.12
flowchart TB
accTitle: Saving a file and recovering at the next startup
accDescr: Write to a temporary file on the same volume, flush, swap while keeping a backup, then validate the primary file at startup and fall back to the backup if needed
tmp["Temporary file on the same volume"] --> write["Write to the end and flush"]
write --> replace["Swap; old contents go to .bak"]
replace -.-> boot["Next startup"]
boot --> valid{"Is the primary file intact?"}
valid -->|"Yes"| main["Read the primary file"]
valid -->|"No"| backup["Fall back to the backup"]
Figure 15: Implement not only the safe way to write but also the way to read when the file is broken.
// The standard pattern for saving settings and data: write to a temporary file, then swap, keeping the old contents
public static void SaveAtomically(string path, string content)
{
string dir = Path.GetDirectoryName(path)!;
string tmp = Path.Combine(dir, Path.GetRandomFileName()); // Create it on the same volume
try
{
using (var fs = new FileStream(tmp, FileMode.CreateNew, FileAccess.Write))
using (var writer = new StreamWriter(fs))
{
writer.Write(content);
writer.Flush();
fs.Flush(flushToDisk: true); // Equivalent to FlushFileBuffers. Writes the OS buffers out
// to disk (the limits of the device-side cache are in Section 8.2)
}
if (File.Exists(path))
File.Replace(tmp, path, path + ".bak"); // Calls ReplaceFile. Keeps the old contents as .bak
else
File.Move(tmp, path);
}
catch
{
// If it fails midway, do not leave the temporary file behind. If periodic saves keep
// failing, complete copies would gradually fill the volume
try { File.Delete(tmp); } catch { /* A failed delete yields to the original exception */ }
throw;
}
}
Even though this example is named SaveAtomically, it does not guarantee atomicity across a power loss. In normal operation it leaves either the complete old file or the complete new file readable, but ReplaceFile is a multi-step namespace operation, and its atomicity against a sudden power loss is not guaranteed by the specification. That is exactly why you keep the .bak and implement the load routine that validates the primary file at startup and falls back to the backup if it is broken.12
The catch in the example exists so that temporary files do not accumulate on ordinary failures and fill the volume. It does not expect this code to run at the moment of a power loss. For append-only logs and CSVs, do not apply the whole-file swap approach as is; use a format that accounts for how it breaks, such as “write one line per record and discard a corrupted last line when loading”.
8.2. Distinguish WriteFile Success from Reaching the Disk
Even when WriteFile succeeds, the data may still be sitting in the OS cache. Windows normally writes into the system buffers and applies the data to disk with lazy writing. At important checkpoints, flush with FlushFileBuffers, or specify FILE_FLAG_WRITE_THROUGH at CreateFile time to request an immediate write. File system metadata is cached too, so committing it involves a flush or write-through.13
Write-through, however, is not the same as “not using the OS cache”. Frequent FlushFileBuffers calls are inefficient, so consider combining it with FILE_FLAG_NO_BUFFERING where needed. In practice, the realistic design is to flush at the points that matter for integrity, such as transaction boundaries and just before closing the file.13
flowchart TB
accTitle: The boundary between write success and persistence
accDescr: An ordinary write reaches storage from the OS cache with a delay; a flush or write-through pushes it to commit, but the constraints of the device-side cache remain
write["WriteFile succeeds"] --> cache["May be in the OS cache"]
cache --> delayed["Lazy writing"]
cache --> flush["Flush at a checkpoint"]
delayed --> device["Applied to storage"]
flush --> device
device -.-> limit["Limits of the device-side cache"]
Figure 16: Do not treat API success, commitment of the OS cache, and resistance to power loss as the same thing.
The volatile cache on the device side has its limits too, and you cannot say “we flushed, so it fully survives a power loss on any hardware”. The relationship between the cache manager, lazy writing, and hardware caches is explained in detail in The Cache Manager: When Does Your WriteFile Reach the Disk?.
8.3. Use the UPS to Turn a Power Loss into a Planned Shutdown
The role of a UPS is not to eliminate outages but to turn a power loss without notification into a planned shutdown with notification. The condition you need is this relationship:
UPS battery runtime > time to detect the switchover + cleanup of apps and services + completion of the OS shutdown
The switchover between AC power and battery, and a low remaining charge, are reported through PBT_APMPOWERSTATUSCHANGE. A GUI receives it through WM_POWERBROADCAST; a service declares SERVICE_ACCEPT_POWEREVENT and then receives SERVICE_CONTROL_POWEREVENT in HandlerEx. WM_POWERBROADCAST is not delivered to a service’s control handler.14
After receiving it, check ACLineStatus and BatteryLifePercent with GetSystemPowerStatus, and proceed to suspending the measurement, saving, and requesting the shutdown.14
flowchart TB
accTitle: From UPS detection to shutdown completion
accDescr: Detect the UPS switching to battery through the appropriate notification path, check the power state, proceed to saving and shutdown, and design the whole sequence to fit within the battery runtime
outage["Outage; the UPS switches to battery"] --> notice["Power notification on the matching path"]
notice --> check["Check the power state and remaining charge"]
check --> save["Suspend the measurement and save"]
save --> req["Request a shutdown from the OS"]
req --> done["Complete the cleanup and the OS exit"]
done -.-> time["Fit the whole sequence within the runtime"]
Figure 17: Fit not only the app but the time until the OS exits into the UPS runtime.
In configurations where Windows sees the UPS as a battery, such as a typical USB-connected UPS, the standard APIs can monitor it. If the vendor’s management software has a “shut down at N% remaining” feature, also check that its threshold is consistent with the cleanup time. Resuming from sleep or hibernation is a separate topic; see Sleep, Hibernation, Modern Standby, and Long-Running Apps.
9. Verification — Confirm Notifications, Timing, and Recovery Results
9.1. Reproduce the Exit Paths on a Test Machine, Not in Production
Do not try things on the production device PC first; use a test machine or a virtual machine such as Hyper-V. On a virtual machine, take a checkpoint beforehand and repeat the tests with test data you can afford to lose.
| Operation to try | What to confirm |
|---|---|
| Sign-out | The GUI’s WM_QUERYENDSESSION → WM_ENDSESSION and the save processing. Not a substitute for verifying a service stop |
shutdown /s /t 0 |
Behavior on a full shutdown |
shutdown /s /hybrid /t 0 |
The hybrid behavior in a configuration that uses fast startup |
shutdown /r /t 0 |
A restart with a full boot, and the recovery afterward |
| Powering off the VM | Whether the next startup recovers even when the guest OS stops without notification |
| Power loss on production-equivalent hardware | Resilience including the physical storage and controller |
A sign-out differs in that the ENDSESSION_LOGOFF bit is set, but it is an easy way to confirm the GUI’s notification path. Do not confuse the full, hybrid, and restart commands; try them separately.15
flowchart TB
accTitle: Widening shutdown verification in stages
accDescr: Try the GUI notifications and each exit operation in the test environment, confirm recovery after a sudden stop on a VM, then verify power-loss resilience including storage on production-equivalent hardware
prep["Prepare the test machine and data"] --> notify["Try the notification paths and exit operations"]
notify --> time["Measure the cleanup time"]
time --> vm["Confirm recovery after a sudden VM stop"]
vm --> real["Verify on real hardware including storage"]
real --> check["Confirm the data and recovery at the next startup"]
Figure 18: Test not only whether the app exits normally but also what it can recover after a sudden stop.
Powering off a VM reproduces only the guest stopping without warning. It cannot reproduce the loss of a physical disk’s volatile cache or controller-dependent corruption, so when shipping as a device PC, do the final confirmation on production-equivalent hardware.
Log a timestamp at the start and end of the cleanup function, and measure whether it fits within the roughly 5 seconds for a GUI and the like, or within the grace period configured for the service. Confirm not only that the exit succeeded but also what was loaded at the next startup and from where work could resume.
9.2. Isolate What Happened Overnight from the Event Log
In the Windows System log, Event ID 1074 records the process that initiated the shutdown, the user, and the reason. For an unexpected shutdown, 41 (Kernel-Power) or 6008 is recorded at the next startup.15
# Check the recent history of shutdown-related events
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41 } -MaxEvents 20 |
Select-Object TimeCreated, Id, ProviderName, Message |
Format-List
If 1074 shows a Windows Update restart and the data was still corrupted, investigate the handling of the exit notification and the save path first. On the other hand, do not conclude power loss from 41 or 6008 alone. They indicate an unexpected termination, and a blue screen or a forced reset are candidates too.15
flowchart TB
accTitle: Choosing what to investigate from the shutdown event log
accDescr: Confirm the initiating process and reason from 1074, and treat 41 and 6008 as clues to an unexpected termination to cross-check with surrounding information such as the bug check code and dumps
log["System event log"] --> normal["1074: initiating process and reason"]
normal --> cleanup["Investigate the notification handling and save path"]
log --> unexpected["41 and 6008: unexpected termination"]
unexpected --> evidence["Check BugcheckCode and dumps"]
evidence --> classify["Isolate crash, power loss, and so on"]
Figure 19: 41 and 6008 are not proof of a power loss itself; they are the entry point to further investigation.
A nonzero BugcheckCode in event 41 is a clue to a crash. If it is 0 and there is no memory dump, a power loss is suspected, but decide by cross-checking with the surrounding information. If it turns out to be a power loss, focus on the save design and the UPS of Section 8; if it was a planned exit, focus on the notifications and cleanup of Sections 3 through 6.
10. Summary — Design Through to the Next Startup, Not Just the Exit Handling
Shutdown handling is not about an event handler that runs once at exit. What matters is making routine saving → a short cleanup → validation and recovery at the next startup one continuous whole.
| Where to review | Design point |
|---|---|
| Routine processing | Save frequently and reduce the delta left at exit |
| GUI exit notifications | Answer the query with TRUE immediately, as a rule. Separate the idempotent save from the post-commit cleanup |
| Console apps and services | Use the notification that fits the app model. Do not rely only on .NET’s ProcessExit or on extending the grace period |
| Uninterruptible work | Combine reason registration and refusal only for as long as needed. Prepare for a forced shutdown too |
| Saving and loading | In addition to the temporary file, flush, and swap, provide a backup and startup validation |
| After a restart | Check the restart registration, the restore data, and the sign-in or service startup path |
In a shutdown with fast startup enabled, the kernel and drivers may come back from hibernation. When isolating trouble, specify “restart” explicitly, and make the app work under both a full shutdown and a hybrid one.5
flowchart TB
accTitle: Final check from exit handling to recovery
accDescr: Keep a saved state during normal processing, do a small cleanup on an exit notification, and validate the data that remains even without notification to recover at the next startup
daily["Keep a saved state routinely"] --> endq{"Is there an exit notification?"}
endq -->|"Yes"| short["Exit with a short cleanup"]
endq -->|"No"| prior["The last saved state is all there is"]
short --> nextboot["Validate and recover at the next startup"]
prior --> nextboot
nextboot --> restart["Return to work under the required conditions"]
Figure 20: Do not separate the implementation of the exit event from routine saving and recovery at the next startup.
Finally, try the notification paths and the time required on a test machine, and confirm the recovery after a sudden stop as well. In after-the-fact investigation, use 1074 / 41 / 6008 as clues to isolate a planned exit from an unexpected termination.
The next time you add a feature, ask “if an exit notification arrives during this processing, or the power is pulled, what will remain at the next startup?” Including that answer in the design is what protects you from discovering data corruption only the next morning.
Related Articles
- How to Replace an EXE or DLL That Is in Use — Restart Manager and the “File in Use” Problem in Automatic Updates
- How to Build and Operate a Windows Service — From Choosing Between Task Scheduler and a Service to Turning a BackgroundService into a Service
- Sleep, Hibernation, Modern Standby, and Long-Running Apps — Preventing “It Stopped in the Middle of the Night” by Design
- The Depths of Windows I/O (Part 4) — The Cache Manager: When Does Your WriteFile Reach the Disk?
- Designing Windows Apps to Leave Logs and Dumps on a Crash
- A Checklist for Handling Child Processes Safely in Windows Apps
Related Consulting Areas
KomuraSoft LLC handles the design and implementation of shutdown and power-loss countermeasures for device PCs and long-running apps, root-cause investigation of data corruption and “it was down in the morning” failures that start from a Windows Update restart or a sign-out, and design reviews of Windows service stop processing and automatic recovery. You are welcome to start from the stage of “something seems to break every time we shut down, but I don’t know where to begin”.
- Windows Application Development
- Bug Investigation and Root-Cause Analysis
- Technical Consulting and Design Review
- Contact Us
References
-
Microsoft Learn, WM_QUERYENDSESSION message. On WM_QUERYENDSESSION being sent when a session ends and apps returning TRUE to respect the user’s intent (DefWindowProc also defaults to TRUE); on deferring cleanup until WM_ENDSESSION; on the system displaying, after 5 seconds, the UI listing apps that are preventing shutdown so that the user can force termination; on the meaning of the ENDSESSION_LOGOFF/CLOSEAPP/CRITICAL bits in lParam; on shutdown and restart being indistinguishable; and on saving data frequently to reduce the amount saved at exit. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, HandlerRoutine callback function. On the CTRL_C/BREAK/CLOSE/LOGOFF/SHUTDOWN events received by a handler registered with SetConsoleCtrlHandler; on the default timeout of CTRL_CLOSE_EVENT being about 5000 milliseconds and that of CTRL_SHUTDOWN_EVENT for service processes about 20000 milliseconds; on CTRL_LOGOFF/SHUTDOWN_EVENT being received in practice only by services because interactive apps are terminated at logoff; and on the handler running on a separate thread. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Service Control Handler Function. On services that declare SERVICE_ACCEPT_PRESHUTDOWN receiving SERVICE_CONTROL_PRESHUTDOWN first, followed by SERVICE_ACCEPT_SHUTDOWN services receiving SERVICE_CONTROL_SHUTDOWN; on the default grace period at shutdown being about 20 seconds with WaitToKillServiceTimeout as the upper limit on an OS restart; on not extending that value; on the control handler returning within 30 seconds, reporting STOP_PENDING with a wait hint, and handing long work to another thread; on finishing cleanup as quickly as possible with UPS operation in mind; and on the SCM not considering dependencies at shutdown by default. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, .NET runtime no longer provides default termination signal handlers. On the runtime no longer providing, from .NET 10, default handlers for Windows CTRL_SHUTDOWN_EVENT/CTRL_CLOSE_EVENT (the equivalents of SIGTERM/SIGHUP on Unix); on the OS default handling terminating the app immediately so that AppDomain.ProcessExit and AssemblyLoadContext.Unloading no longer fire; and on signal handling appropriate to the app model being registered by higher-level libraries or app code. ↩ ↩2 ↩3
-
Microsoft Learn, Fast startup causes hibernation or shutdown to fail in Windows 10 or Windows 8.1. On the kernel session not being closed but hibernated under fast startup, with the kernel and device driver state saved to hiberfil.sys; on “Restart” always performing a full boot because a completely new Windows state is required; on fast startup being enabled by default and disabling it not being recommended; and on Shutdown.exe defaulting to a full shutdown with the /hybrid option giving the hybrid behavior. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Shutdown Changes for Windows Vista. On responses to WM_QUERYENDSESSION/WM_ENDSESSION being deferrable by 5 seconds each, after which the user chooses to continue or cancel; on console apps and apps without a visible window being unable to cancel shutdown and being terminated automatically after 5 seconds without a response or on a FALSE response; on registering a reason with ShutdownBlockReasonCreate when blocking is needed; and on apps not depending on being able to block shutdown. ↩ ↩2 ↩3
-
Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). On calling it at the start of uninterruptible work to register a reason string and calling ShutdownBlockReasonDestroy on completion; on it being callable only from the thread that created the window; and on keeping the string short and clear because the user reads the reason for only a few seconds. ↩ ↩2 ↩3
-
Microsoft Learn, SetConsoleCtrlHandler function. On a process that has loaded gdi32.dll or user32.dll being treated as a Windows app whose CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT handlers are not called; on the workaround of creating a hidden window and handling WM_QUERYENDSESSION/WM_ENDSESSION; and on console functions possibly not working normally during signal handling. ↩
-
Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). On the SCM waiting after the PRESHUTDOWN notification until the service stops or times out; on the default timeout being 10 seconds from Windows 10 Creators Update (build 15063) onward and 3 minutes before that; on configuring it with ChangeServiceConfig2; and on status updates continuing during SERVICE_STOP_PENDING. ↩ ↩2
-
Microsoft Learn, RegisterApplicationRestart function (winbase.h). On registering for restart in the crash, not-responding, update, and update-driven computer-restart scenarios; on specifying command-line arguments for the restart; on registering before a problem occurs, with WM_QUERYENDSESSION handling being the last opportunity in the update scenario; on processes running for less than 60 seconds not being restarted; on restart after a crash or hang requiring the user’s consent; and on spanning an OS restart requiring a shutdown with EWX_RESTARTAPPS/SHUTDOWN_RESTARTAPPS. ↩ ↩2
-
Microsoft Learn, Winlogon automatic restart sign-on (ARSO). On Windows Update saving the credentials of the last interactive user and configuring Autologon when it starts an automatic restart; on the user being signed in automatically after the restart and the session locked; on the saved credentials being deleted after a successful sign-in; and on configuration through Group Policy (DisableAutomaticRestartSignOn and others). ↩ ↩2
-
Microsoft Learn, ReplaceFileW function (winbase.h). On ReplaceFile combining into one function the multiple steps corresponding to saving to a new file, temporarily renaming the original, renaming the new file, and deleting the original; on it preserving the original file’s attributes such as creation time, DACL, encryption, compression, and named streams; and on the backup, the replaced file, and the replacement file having to be on the same volume. ↩ ↩2
-
Microsoft Learn, File Caching. On writes going to the system cache by default and being applied to disk by lazy writing; on FILE_FLAG_WRITE_THROUGH writing data to disk immediately; on FlushFileBuffers flushing explicitly; and on file system metadata always being cached, so that committing metadata requires a flush or write-through. ↩ ↩2
-
Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. On this event being delivered through WM_POWERBROADCAST when switching between battery and AC power or when the remaining charge drops; and on calling GetSystemPowerStatus upon receipt to check ACLineStatus, BatteryFlag, BatteryLifePercent, and other members of SYSTEM_POWER_STATUS. ↩ ↩2
-
Microsoft Learn, Troubleshoot unexpected reboots using system event logs. On a normal restart recording Event ID 1074 (which process initiated the shutdown, on whose behalf, and for what reason); on an unexpected restart recording Event ID 41 (Kernel-Power) and 6008 (the previous shutdown was unexpected); and on these IDs being used to isolate the kind of restart. ↩ ↩2
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
What Fast Startup Really Does — Why a Windows 'Shutdown' Is Not the Same as a Restart
A Windows shutdown is a hybrid shutdown by default, saving the kernel and drivers to hiberfil.sys. Why only a restart resets them, and wh...
The Network Works but Windows Says "No internet" — Isolating NCSI, DNS, Proxy, and VPN on Windows
Why Windows says "No internet" while the network works, starting from the NCSI verdict. Isolate DNS, proxy, VPN, and captive portals with...
Time Travel Debugging — Recording and Rewinding the Bugs That Never Reproduce in Long-Running Apps
A once-a-month bug leaves only its result in a crash dump. Record and rewind execution with WinDbg Time Travel Debugging (TTD): TTD.exe, ...
Why Arguments Break — The Rules of Windows Command-Line Arguments
Windows passes CreateProcess a single string that the receiver splits. Covers the CommandLineToArgvW, CRT, and .NET rules, ArgumentList, ...
End of Servicing for Windows Printer Drivers — How Business Apps Should Prepare Their Report and Label Printing
Microsoft is phasing out v3/v4 printer drivers. What Windows protected print mode removes, and how to inventory and prepare report and la...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Frequently Asked Questions
Common questions about the topic of this article.
- A problem that "Shut down" did not fix went away after a "Restart". Why?
- On client OSes from Windows 8 onward, when fast startup is enabled (the default on many PCs that support hibernation), "Shut down" works through a mechanism called hybrid shutdown. The user is signed out, but the state of the kernel and drivers is saved to the hibernation file and restored as is on the next boot. In other words, the core of the OS has not been reset. "Restart", on the other hand, always performs a full boot, so trouble in drivers and services is reset. In your troubleshooting procedure, write "restart" explicitly rather than "shut down and power back on". If you want a full shutdown from the command line, shutdown /s does it.
- Can I hold off the shutdown until my app finishes saving?
- You can ask it to wait temporarily, but you cannot stop it reliably. If you register a reason string with ShutdownBlockReasonCreate only while an operation that cannot be interrupted is running, that reason appears on the "This app is preventing shutdown" screen and the user can decide whether to continue or cancel. The user can still choose to force the shutdown, though, and a forced shutdown or an update-driven restart may not wait at all. The proper approach is therefore not to block, but to reduce the data at risk with frequent autosave and to design cleanup that finishes within a few seconds of the exit notification.
- Stopping my Windows service takes a long time. Can the grace period at shutdown be extended?
- In the default configuration, which receives SERVICE_CONTROL_SHUTDOWN, the grace period is roughly 20 seconds and depends on the WaitToKillServiceTimeout registry value. Rewriting that value from the app side to extend it is not recommended. If you need a longer grace period, you can declare SERVICE_ACCEPT_PRESHUTDOWN and receive SERVICE_CONTROL_PRESHUTDOWN; it is delivered before the others, and the timeout can be configured with ChangeServiceConfig2 (the default is 10 seconds from Windows 10 Creators Update onward and 3 minutes before that). PRESHUTDOWN, however, holds up the entire shutdown for that interval, so limit it to cases that truly need it, and fundamentally design the stop processing itself to finish in a few seconds.
- Is it safe to do shutdown cleanup in .NET's AppDomain.ProcessExit?
- I recommend not relying on it. Historically the runtime registered default signal handlers, and the ProcessExit event fired on CTRL_CLOSE_EVENT and CTRL_SHUTDOWN_EVENT, but from .NET 10 the runtime no longer provides default termination signal handlers, and ProcessExit no longer fires in those situations. Implement cleanup on the notification path that fits your app model: for GUI apps, FormClosing or SessionEnding (these are query-phase notifications, so limit them to idempotent saves and do cleanup that is only possible after the exit is committed in a WM_ENDSESSION hook); for Generic Host / Worker Service, IHostApplicationLifetime and StopAsync; for console apps, SetConsoleCtrlHandler or PosixSignalRegistration.
- How do I keep files from being corrupted by a sudden power loss?
- A power loss brings no notification at all, so the only option is to write in a way that does not break no matter when the power is cut. The baseline is not to overwrite the original file in place: write everything to a temporary file on the same volume, flush it, and swap it in with ReplaceFile (File.Replace in .NET). In normal operation this leaves you able to read whichever of the old or new file is complete, but ReplaceFile's atomicity across a power loss is not guaranteed by the specification, so keep a backup (the third argument) and implement, as one set, a load routine that validates the primary file at startup and falls back to the backup if it is broken. In addition, a successful WriteFile does not mean the data reached the disk, so at important checkpoints commit the write with FlushFileBuffers or FILE_FLAG_WRITE_THROUGH. On device PCs the standard configuration adds a UPS, detects the switch to battery power, and leads into a safe shutdown.