You’ve rebooted a server after applying a critical patch, only to find that your automation script didn’t execute. It’s a frustrating scenario that hits every sysadmin at some point. You checked the permissions, verified the path, and restarted again, but the RunOnce registry key is still sitting there, untouched. Or worse, it’s gone, and you have no idea if the command actually ran.
This happens because most people treat the RunOnce mechanism as a simple "start me once" switch, ignoring the nuances of how Windows handles system initialization. Unlike the persistent Run key, which sticks around every login, RunOnce is designed for one-time execution during a specific phase of the boot process. It’s ephemeral. When the system successfully executes the command, the key deletes itself. If it fails, it often stays behind, creating a persistent state that can confuse diagnostics.
In this guide, we’re going to demystify the RunOnce registry keys. We’ll look at the syntax quirks that trip up developers, the specific privileges required for HKLM vs. HKCU entries, and how to use these keys safely without becoming a persistence vector for malware. Whether you’re debugging a failed deployment or designing a secure startup routine, understanding the mechanics behind system initialization is key.
Understanding Windows RunOnce Mechanics & LSI Context
The Four Registry Keys: HKLM vs HKCU
Windows provides four specific locations where you can place a RunOnce command. Confusing which one you need is the first step to a script that never runs.
The machine-level keys are located under:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
And the user-level keys are under:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce
Here’s the catch: execution context matters more than you think.
If you place an entry in HKLM, it runs in the context of the logging-on user. However, according to Microsoft’s documentation, HKLM\...\RunOnce entries generally only execute if the user belongs to the Administrators group. This is a security feature, not a bug. If a standard user logs in, the system ignores the machine-level RunOnce keys. In my experience, this is the number one reason why "it worked on my test machine but not on the user's PC." Your test machine likely had the tester logged in as an admin, while the end-user was a standard account.
For HKCU, the situation is simpler. The command runs when that specific user logs on. It executes in the user’s context, meaning it has the same permissions as the user. This makes HKCU the safer default for user-specific initialization tasks, like launching a configuration dialog or setting up a user profile.
Syntax Modifiers: Exclamation Point (!) and Asterisk (*)
You won’t find this in the basic "how to add a registry key" tutorials, but the prefix you put on the value name changes everything.
By default, Windows deletes the RunOnce value before it runs the command. Why? To prevent infinite loops. If the command fails, you don’t want it running on the next boot either (unless you explicitly want that behavior, which is rare).
However, this default behavior makes debugging a nightmare. If your script fails, the key is already gone, and you’re left with no trace of what was supposed to run. To fix this, you can prefix the value name with an exclamation mark (!).
!MySetupScript = C:\Scripts\setup.bat
When Windows sees the !, it defers the deletion until after the command attempts to run. If the command fails, the key remains, and you can inspect it later. This is a critical debugging tool that I recommend you use for every production deployment until you’re confident in the script’s idempotency.
There’s another modifier: the asterisk (*). By default, RunOnce keys are ignored if the computer boots into Safe Mode. This is good for security, but bad if you’re trying to repair a broken system. Prefix the value name with * to force execution in Safe Mode:
*RecoveryTool = C:\Tools\repair.exe
Use this sparingly. Forcing execution in Safe Mode bypasses some of the security constraints of a standard boot, and you need to be very sure that the executable is trusted.
Step-by-Step: Configuring RunOnce Scripts via GUI and CMD
Method 1: Using Registry Editor (GUI)
For a quick, one-off configuration, the GUI is still the fastest route.
- Open
regeditwith Run as Administrator. If you’re touchingHKLM, you need elevated privileges. - Navigate to
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce. - Right-click in the right pane and select New > String Value.
- Name the value. Remember, this name is the "identifier." Prefix it with
!if you want debugging retention. - Double-click the new value and paste your command line.
Keep the command line under 260 characters. This is a hard limit in Windows. I’ve lost more hours to silently truncated command lines than to anything else. If you’re launching a Python script with long arguments, consider using a batch file wrapper instead of trying to cram everything into the registry value.
Method 2: Command Line & PowerShell Automation
You won’t be doing this manually in an enterprise environment. You need scriptability.
From CMD, the reg command is your friend:
reg add "HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce" /v "!InitScript" /t REG_SZ /d "C:\Scripts\init.bat" /f
In PowerShell, which I prefer for its error handling, you can use New-ItemProperty. Note that you must ensure the registry path exists first, though RunOnce usually does:
New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce" `
-Name "!InitScript" `
-Value "C:\Scripts\init.bat" `
-PropertyType String -Force
The -Force flag is important here. If you’re running this in a deployment pipeline, you don’t want a script to fail because the entry already exists from a previous failed attempt. It overwrites the existing value, giving you a clean slate for the next reboot.
RunOnce Troubleshooting Checklist: Why Isn't My Script Running?
Diagnosing Execution Failures
When the key is still there after a reboot, it’s not necessarily because the script failed. It might be because the conditions for execution weren’t met.
Start with the user context. Is the logged-in user an administrator? If you put the key in HKLM and a standard user logs in, it won’t run. Check the user’s group membership.
Next, look at the path. Is the executable actually accessible? If you’re using a UNC path (\\server\share\...), make sure the user has network drive access at the time of logon. This is a subtle timing issue. Sometimes, network drives aren’t mapped until after the RunOnce phase. If your script depends on a mapped drive, it will fail. Use the full UNC path in the command, not a mapped drive letter.
Finally, check the Event Viewer. Specifically, look at Applications and Services Logs > Microsoft > Windows > Application Experience. Sometimes, Windows logs a specific error code when it tries to execute a RunOnce entry and fails. If you’re using the ! prefix, you can check the registry again. If the value is still there, the command likely failed. Open a new command prompt and manually run the exact string you see in the registry. If it works there, the issue is likely with execution context or permissions during the boot sequence.
Safe Mode & Persistence Conflicts
This is where things get weird.
If you boot into Safe Mode, RunOnce keys are ignored unless they start with *. So, if your recovery tool is stuck in RunOnce without the asterisk, it will never run in Safe Mode.
There’s also a subtle distinction between "initial setup" and "persistence." RunOnce is not a persistence mechanism. It’s a transient one. If you keep re-adding the key on every boot, you’re fighting the OS. The system expects the key to be gone after one successful run. If you need something to run every time, use the Run key or a Scheduled Task. Misusing RunOnce for persistent tasks creates a state where the key is constantly being recreated and deleted, which can interfere with Windows Setup services.
I’ve seen cases where a third-party agent would keep re-injecting itself into RunOnce on every boot. The result? A system that looked healthy but had a constantly changing registry state that confused security scanners. If your script is supposed to run once, make sure it deletes its own trigger or relies on the OS to do it.
Security Implications & Defense Against RunOnce Abuse
RunOnce as a Malware Persistence Mechanism
Let’s be clear: RunOnce is a well-known target for attackers. Why? Because it’s transient. It’s harder to detect than a persistent Run key because it disappears after execution.
Malware families use RunOnce to drop secondary payloads after a reboot. They install the main payload, set a RunOnce entry to execute the next stage, and then delete themselves. By the time your antivirus scans the system, the initial dropper is gone, and the new process is running from a less suspicious location.
This falls under the category of LOLBAS (Living Off The Land Binaries and Scripts). Attackers use legitimate system mechanisms to execute malicious code. The RunOnce key is a prime example. It’s a native Windows feature, so it doesn’t trigger file-system-based malware detection.
You can’t just "disable" RunOnce without breaking legitimate software installations. Instead, you monitor for anomalies. Look for RunOnce entries that point to suspicious directories like %Temp% or %AppData%. No legitimate software should be running its initialization script from a temporary folder.
Best Practices for Sysadmins
Your defense in depth starts with auditing.
Regularly scan for unauthorized RunOnce entries. A simple PowerShell script can list them out:
Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce" | Select-Object *
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce" | Select-Object *
Run this on a schedule and log the output. If you see a new entry that you didn’t deploy, investigate it.
Second, embrace idempotency. When you write your scripts, ensure that running them multiple times doesn’t cause issues. This is crucial because if a RunOnce entry fails to delete itself (or if you use the ! prefix and the script fails), it might run again on the next boot. A script that says "install X" should first check if "X" is already installed. If it is, it should exit cleanly. This makes your system resilient to the quirks of the RunOnce lifecycle.
Finally, consider if you actually need RunOnce. For complex, recurring, or scheduled tasks, a Scheduled Task is often a better fit. It provides logging, retry policies, and clearer separation from the boot process. Use RunOnce only for that specific "after-reboot-once" scenario.
RunOnce vs. Alternatives: Python, Batch, & Other OS Strategies
When to Use Python or Batch for One-Time Logic
Not every "run once" requirement fits a registry key. Sometimes, you’re dealing with a development workflow or a cross-platform script where registry access isn’t available or isn’t the right tool.
In Python, you don’t use the RunOnce registry key. Instead, you implement the logic yourself. The common pattern is using a flag file.
import os
import subprocess
FLAG_FILE = "C:\Scripts\setup_complete.flag"
if not os.path.exists(FLAG_FILE):
subprocess.run(["C:\\Scripts\\do_work.py"])
with open(FLAG_FILE, 'w') as f:
f.write("completed")
This gives you control. You can log the execution, handle errors, and decide when to delete the flag. It’s more robust than relying on the OS to delete a registry key.
In Batch, it’s similar but cruder:
if not exist "%~dp0\done.flag" (
call C:\Scripts\work.bat
echo true > "%~dp0\done.flag"
)
Here’s a quick comparison of when to use which:
| Method | Use Case | Pros | Cons |
|---|---|---|---|
| RunOnce Key | Post-install config, one-shot reboot | Native OS integration, no extra files | Limited debugging, permission traps, transient nature makes it hard to trace |
| Startup Folder | Persistent user apps | Simple, user-visible | Persists forever, hard to remove if forgotten |
| Scheduled Task | Complex, recurring, or conditional runs | Logging, retry logic, runs as system/user | Overkill for simple one-off tasks, complex to set up |
| Flag File (Python/Batch) | Dev workflows, cross-platform | Full control, testable, portable | Requires the main process to be scheduled or manually triggered |
| I’ve found that the registry approach is best when you’re working within a Windows-centric enterprise deployment where GPO and SCCM are managing the environment. For individual developer scripts or lightweight tools, the flag file approach is usually cleaner and easier to debug. |
FAQ
What is the difference between Run and RunOnce in Windows?
The Run key is persistent. It executes every time the user logs in. The RunOnce key is ephemeral. It executes once, and then the registry value is automatically deleted by the system after the command runs (or is deferred with the ! prefix). Use Run for applications that need to start every session, and RunOnce for one-time initialization tasks.
Why is my RunOnce key not running on reboot? Three common causes: 1) The user logging in is not an administrator (required for HKLM keys). 2) The command line exceeds the 260-character limit and is being truncated. 3) The path to the executable is incorrect or inaccessible (e.g., using a mapped drive letter instead of a UNC path). Always test the command manually in CMD with the same user context.
Can I run a Python script using a RunOnce registry key?
Yes. Set the value data to the full path of the Python executable and your script. For example: "C:\Python39\python.exe" "C:\Scripts\my_script.py". Ensure the user has permission to execute both files and that Python is in the PATH or referenced by absolute path.
How do I delete RunOnce entries from the registry?
You can delete them manually in regedit by right-clicking the value and selecting Delete. Or use the command line: reg delete "HKCU\...\RunOnce" /v "YourValueName" /f. Keep in mind that if the key was added with the ! prefix and the script failed, it will remain until you manually remove it or the script succeeds on a subsequent boot.
Conclusion
The RunOnce registry key is a powerful, if underappreciated, tool in the Windows sysadmin’s toolkit. It’s not a replacement for a full deployment solution, but for handling that critical "after-reboot" step, it’s hard to beat.
The key to mastering it is understanding its ephemeral nature. It’s designed to clean up after itself, which means your debugging strategies need to account for the key disappearing. Use the ! prefix for your initial tests to keep the entry around for troubleshooting. Verify that your scripts are idempotent so that accidental re-execution doesn’t break your system.
And always, always consider the security implications. A RunOnce entry that points to a temp folder is a red flag. Keep your registry clean, audit your entries, and prefer Scheduled Tasks for anything that needs to happen more than once.
Ready to audit your own environment? Download our free RunOnce Troubleshooting Checklist PDF and run a quick scan of your HKLM and HKCU keys to ensure you’re not carrying any dormant or malicious entries.