You're running an installer, a deployment script, or a Windows Update call, and instead of a clean zero you get 0x00000BC2. The name is the giveaway: ERROR_SUCCESS_REBOOT_REQUIRED. Nothing failed. Windows is telling you the operation finished but the changes are parked in a pending state until the machine restarts. People see the word "error" and start troubleshooting a problem that doesn't exist.
What's actually happening here is that the component you installed replaced files that were locked by a running process, or wrote registry keys that only load at boot. Windows stages the new versions, queues the swap for the next startup, and returns 0x00000BC2 so your automation knows to schedule that restart. An MSI returns the same condition as exit code 3010; an InitiateSystemShutdown call returns it as ERROR_SUCCESS_REBOOT_INITIATED (which is a different number, 0x0000045C, so don't confuse the two).
The real question isn't "how do I fix 0x00000BC2." It's "why did my process surface it when I expected a plain success." There are three reasons, and they stack in a specific order of likelihood.
Cause 1: An installer replaced a file that was in use (MSI return code 3010)
This is the one you'll hit nine times out of ten. You push a patch, a driver package, or a .NET runtime update while the target files are held open. Windows can't delete or overwrite a loaded DLL or a running service binary, so it renames the old one to a pending-rename entry and defers the actual replacement to the next boot. The install itself returns success. The reboot flag is the receipt.
A concrete trigger: you're rolling out a Visual C++ redistributable to a fleet of Windows 10 22H2 machines, and msiexec /i vc_redist.x64.exe /quiet /norestart runs while explorer.exe and a handful of apps have the old CRT DLLs mapped. You get 3010 back on every box that had those DLLs loaded. Machines that were fresh or idle return 0. Same package, different result, purely because of what was running.
For a single machine, just reboot. That's the whole fix. For automation, you need to treat 3010 as success-with-a-caveat rather than a failure:
msiexec /i "package.msi" /quiet /norestart
if %ERRORLEVEL%==3010 (
echo Install succeeded, reboot pending
shutdown /r /t 60 /c "Reboot required by package install" /d p:4:1
) else if %ERRORLEVEL%==0 (
echo Install complete, no reboot needed
) else (
echo Real failure: %ERRORLEVEL%
exit /b %ERRORLEVEL%
)
In PowerShell, the pattern is the same but cleaner:
$p = Start-Process msiexec.exe -ArgumentList '/i','package.msi','/quiet','/norestart' -Wait -PassThru
switch ($p.ExitCode) {
0 { 'Success' }
3010 { 'Success, reboot required' }
1641 { 'Success, reboot already initiated' }
default { throw "Install failed: $($p.ExitCode)" }
}
Note the second code, 1641 (ERROR_SUCCESS_REBOOT_INITIATED). If your package used /forcerestart or the MSI had REBOOT=Force set, you'll see that instead. It also means success. Don't ignore it.
Cause 2: A pending reboot was already queued before your process ran
Sometimes your installer didn't do anything wrong. Windows was already sitting on a pending operation from an earlier Windows Update, a driver install, or a failed uninstall that left a tombstone behind. Your call just surfaced the existing state.
The reason this matters: if you're sequencing installs, a stale pending reboot can make the second install return 3010 even though the second package needed no restart of its own. You end up rebooting for no reason, or worse, your orchestration tool marks the step as incomplete.
Check for pending reboots before you start. Four registry locations give the game away:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPendingHKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequiredHKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperationsHKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending
Any one of those keys existing means a reboot is already owed. PowerShell one-liner to check all of them:
function Test-PendingReboot {
$paths = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending'
)
foreach ($p in $paths) { if (Test-Path $p) { return $true } }
$pfro = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name PendingFileRenameOperations -ErrorAction SilentlyContinue)
if ($pfro.PendingFileRenameOperations) { return $true }
return $false
}
Wire that into your deploy script's preamble. If it returns $true, reboot first, then run your install. You'll get an honest answer instead of a 3010 that belongs to somebody else's unfinished business.
Cause 3: You're catching the code and treating it as an exception
This one is a logic bug in your own code, not a Windows condition. A lot of installer wrappers do a blanket "if return code != 0, throw" check. 0x00000BC2 isn't zero, so it throws, your CI pipeline goes red, and someone spends an afternoon chasing a phantom.
The fix is to widen your success set. Any return code in the 0x00000BC2, 3010, or 1641 family is fine — it just means "done, restart later." Here's the shape of the check you want, in C#:
const int ERROR_SUCCESS = 0;
const int ERROR_SUCCESS_REBOOT_REQUIRED = 0x00000BC2; // 3010
const int ERROR_SUCCESS_REBOOT_INITIATED = 0x0000045C; // 1641
bool Succeeded(int code) =>
code == ERROR_SUCCESS ||
code == ERROR_SUCCESS_REBOOT_REQUIRED ||
code == ERROR_SUCCESS_REBOOT_INITIATED;
If your wrapper logs exit codes to a monitoring system, tag 0x00000BC2 as a warning, not an error. That way the reboot demand is visible without paging anyone at 3 a.m.
One thing to be careful about: don't call InitiateSystemShutdown with the abort flag and then immediately re-issue, expecting a different result. If a reboot is already pending, Windows keeps returning the reboot-required code until the machine actually restarts. Retrying the operation just re-stages the same pending changes.
When it's not actually benign
Two edge cases are worth knowing. First, if you see 0x00000BC2 repeatedly from a scheduled task on the same machine and the machine never reboots, the pending operation never completes, and the next run stacks another one on top. You can end up with a PendingFileRenameOperations entry list hundreds deep. That's a real problem and the symptom is a machine that hangs at boot. If you find that, clear the stale entries manually or run the System File Checker, then reboot cleanly.
Second, if the value appears during a Windows Update scan and the reboot never clears the flag, check for a stuck service — usually TrustedInstaller or wuauserv refusing to release a lock. Restart those services manually and try the update again.
Quick reference
| Cause | How to confirm | Fix |
|---|---|---|
| Installer replaced a locked file | MSI exit code 3010 or 1641 | Reboot. Treat 3010 as success in scripts. |
| Stale pending reboot from earlier work | Check RebootPending, RebootRequired, PendingFileRenameOperations keys | Reboot before starting the new install. |
| Code treats non-zero as failure | Pipeline fails on 0x00000BC2 but install logs show success | Whitelist 0x00000BC2, 3010, 1641 as success states. |
| Reboot flag never clears | Repeated 0x00000BC2 across days, machine won't restart cleanly | Clear stale PendingFileRenameOperations, SFC, restart TrustedInstaller/wuauserv, reboot. |
Bottom line: 0x00000BC2 is Windows being polite. It's saying "I did what you asked, now restart me." Don't debug it. Don't suppress it. Just schedule the reboot and move on.