How to Monitor Registry Changes in Batch Script
The Windows Registry stores everything from application settings and driver configurations to security policies. Monitoring the Registry for changes is a critical task for auditing software installers, detecting unauthorized security modifications, or tracking configuration changes. While Batch cannot natively watch the registry in real-time, you can use the reg query command to snapshot keys and detect differences over time, or use PowerShell's WMI event subscription for event-driven monitoring.
This guide will explain how to build a registry monitor in Batch.
Method 1: Snapshot and Compare (Recommended for Most Use Cases)
The most reliable approach for detecting registry changes is to export a key to a text file, wait, and compare it to a fresh export. This detects value additions, deletions, and modifications within the monitored key.
Implementation
@echo off
setlocal EnableDelayedExpansion
set "RegKey=%~1"
set "Interval=10"
set "LogFile=%~dp0registry_audit.log"
set "SnapPrev=%TEMP%\regsnap_prev_%RANDOM%.txt"
set "SnapCurr=%TEMP%\regsnap_curr_%RANDOM%.txt"
:: Validate argument
if "%RegKey%"=="" (
echo Usage: %~nx0 ^<RegistryKeyPath^>
echo.
echo Examples:
echo %~nx0 "HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
echo %~nx0 "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
endlocal
exit /b 1
)
:: Verify the key exists and is accessible
reg query "%RegKey%" >nul 2>&1
if errorlevel 1 (
echo [ERROR] Cannot access registry key: %RegKey% >&2
echo This key may not exist or may require administrator privileges. >&2
endlocal
exit /b 1
)
title Registry Monitor: %RegKey%
echo [MONITOR] Watching: %RegKey%
echo [MONITOR] Polling every %Interval% seconds. Press Ctrl+C to stop.
echo [MONITOR] Log file: %LogFile%
echo ------------------------------------------
:: Take initial snapshot
reg query "%RegKey%" > "%SnapPrev%" 2>nul
:MonitorLoop
timeout /t %Interval% >nul
:: Verify key still exists (may be deleted by an uninstaller)
reg query "%RegKey%" >nul 2>&1
if errorlevel 1 (
echo [%date% %time%] [WARNING] Key is no longer accessible: %RegKey% >&2
echo [%date% %time%] [WARNING] Key inaccessible: %RegKey% >> "%LogFile%"
goto :MonitorLoop
)
:: Take current snapshot
reg query "%RegKey%" > "%SnapCurr%" 2>nul
:: Compare snapshots
fc "%SnapPrev%" "%SnapCurr%" >nul 2>&1
if errorlevel 1 (
echo [%date% %time%] [ALERT] Registry change detected!
:: Log the detection
echo [%date% %time%] Change detected in: %RegKey% >> "%LogFile%"
:: Show what changed, lines only in the new snapshot (additions/changes)
echo New or modified values: >> "%LogFile%"
for /f "delims=" %%d in ('findstr /v /x /g:"%SnapPrev%" "%SnapCurr%"') do (
echo %%d
echo %%d >> "%LogFile%"
)
:: Show what was removed, lines only in the old snapshot
echo Removed values: >> "%LogFile%"
for /f "delims=" %%d in ('findstr /v /x /g:"%SnapCurr%" "%SnapPrev%"') do (
echo [REMOVED] %%d
echo [REMOVED] %%d >> "%LogFile%"
)
echo. >> "%LogFile%"
:: Update the baseline
copy /y "%SnapCurr%" "%SnapPrev%" >nul
)
goto :MonitorLoop
How this works:
reg queryexports all values under the specified key to a text file.fccompares the previous and current exports byte-by-byte.- If the files differ,
findstr /v /x /g:cross-references the two files to identify specifically which lines were added, changed, or removed. - The baseline is updated so the next comparison starts from the new state.
What this detects:
| Change Type | Detected? |
|---|---|
| Value data modified | Yes |
| New value added | Yes |
| Value deleted | Yes |
| Subkey added or removed | Partial (only if reg query lists it) |
What this does NOT detect:
Changes in subkeys below the monitored key are not detected unless you add the /s (recursive) flag to reg query. Use /s cautiously, deeply nested keys with many values produce large snapshots and slow comparisons.
Method 2: WMI Event-Driven Monitoring (PowerShell)
For scenarios requiring immediate notification without polling delays, WMI provides registry change event classes. This method blocks until a change occurs, then returns to Batch for processing.
Monitoring a Specific Key
<# :
@echo off
setlocal
:: Define Variables
set "Hive=HKEY_CURRENT_USER"
:: Note: Keep the double backslashes (\\). WMI requires them for escaping paths.
set "KeyPath=Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
set "LogFile=%~dp0registry_events.log"
set "DisplayKey=HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
title Registry Event Monitor: %DisplayKey%
echo [MONITOR] Event-driven monitoring: %DisplayKey%
echo[MONITOR] Waiting for changes (press Ctrl+C to stop^)...
echo [MONITOR] Log file: %LogFile%
echo ------------------------------------------
:: Launch the PowerShell code embedded at the bottom of this file
powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((Get-Content '%~f0') -join \"`n\")"
exit /b %errorlevel%
#>
# --- EVERYTHING BELOW THIS LINE IS POWERSHELL ---
$hive = $env:Hive
$keyPath = $env:KeyPath
$logFile = $env:LogFile
$displayKey = $env:DisplayKey
# -------------------------------------------------------------------
# CRITICAL FIX 1: WMI does not support "HKEY_CURRENT_USER".
# We must translate it to HKEY_USERS and append the current user's SID.
# -------------------------------------------------------------------
if ($hive -eq "HKEY_CURRENT_USER") {
$sid =[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$hive = "HKEY_USERS"
# Prepend SID to the KeyPath (ensure double backslash for WQL query escaping)
$keyPath = "$sid\\$keyPath"
}
# -------------------------------------------------------------------
# CRITICAL FIX 2: Changed from 'RegistryValueChangeEvent' to 'RegistryKeyChangeEvent'
# because we are monitoring an entire key folder, not one specific value name.
# -------------------------------------------------------------------
$query = "SELECT * FROM RegistryKeyChangeEvent WHERE Hive='$hive' AND KeyPath='$keyPath'"
try {
# Initialize the WMI event watcher
$watcher = New-Object System.Management.ManagementEventWatcher($query)
# Infinite loop. This is totally passive and uses ~0% CPU.
while ($true) {
$event = $watcher.WaitForNextEvent()
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Host "[$ts] Registry change detected in: $displayKey" -ForegroundColor Cyan
# Log the timestamp
"[$ts] Change detected in: $displayKey" | Out-File -FilePath $logFile -Append
# Query the registry to capture the new state and append to log
$regOutput = reg.exe query $displayKey 2>$null
if ($regOutput) {
$regOutput | Out-File -FilePath $logFile -Append
}
"" | Out-File -FilePath $logFile -Append
}
}
catch {
Write-Host "[ERROR] WMI event subscription failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "[DEBUG] Attempted Query: $query" -ForegroundColor Yellow
exit 1
}
finally {
if ($watcher) { $watcher.Dispose() }
}
WMI Registry event classes:
| WMI Class | Detects |
|---|---|
RegistryValueChangeEvent | A value under the key was modified, added, or deleted |
RegistryKeyChangeEvent | A subkey was added or deleted |
RegistryTreeChangeEvent | Any change anywhere in the subtree |
Important notes:
- Double backslashes required: WMI queries use WQL (a SQL-like language) where backslashes must be doubled:
Software\\MicrosoftnotSoftware\Microsoft. - Hive names must be full: Use
HKEY_CURRENT_USERnotHKCU, andHKEY_LOCAL_MACHINEnotHKLM. WMI does not recognize the short-form abbreviations. - No detail on WHAT changed: WMI registry events tell you that something changed but do not report which specific value was modified or what the new value is. You must query the key afterward (with
reg query) to determine the current state. - HKLM monitoring may require admin rights: Subscribing to events on
HKEY_LOCAL_MACHINEkeys typically requires elevation.
Timeout handling:
The watcher uses a 60-second timeout. When it expires, the script loops back and creates a new watcher. This prevents the PowerShell process from blocking indefinitely and ensures Ctrl+C works.
Method 3: Monitoring Startup Entries
A common security use case is watching the Run keys for new programs that register to start automatically, a technique used by both legitimate software and malware.
@echo off
setlocal EnableDelayedExpansion
set "LogFile=%~dp0startup_audit.log"
set "Interval=30"
:: Define the keys to monitor (both per-user and system-wide)
set "Key[0]=HKCU\Software\Microsoft\Windows\CurrentVersion\Run"
set "Key[1]=HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
set "Key[2]=HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce"
set "Key[3]=HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
set "KeyCount=4"
title Startup Entry Monitor
echo [MONITOR] Watching startup registry keys for new entries.
echo [MONITOR] Polling every %Interval% seconds. Press Ctrl+C to stop.
echo ------------------------------------------
:: Take initial snapshots
for /L %%i in (0, 1, 3) do (
set "SnapFile=%TEMP%\startup_snap_%%i_%RANDOM%.txt"
reg query "!Key[%%i]!" > "!SnapFile!" 2>nul
set "Snap[%%i]=!SnapFile!"
)
:StartupLoop
timeout /t %Interval% >nul
for /L %%i in (0, 1, 3) do (
set "CurrSnap=%TEMP%\startup_curr_%%i.txt"
reg query "!Key[%%i]!" > "!CurrSnap!" 2>nul
fc "!Snap[%%i]!" "!CurrSnap!" >nul 2>&1
if errorlevel 1 (
echo [%date% %time%] [ALERT] Change detected in: !Key[%%i]!
:: Identify new entries
for /f "delims=" %%d in ('findstr /v /x /g:"!Snap[%%i]!" "!CurrSnap!"') do (
echo [NEW] %%d
echo [%date% %time%] NEW in !Key[%%i]!: %%d >> "%LogFile%"
)
:: Identify removed entries
for /f "delims=" %%d in ('findstr /v /x /g:"!CurrSnap!" "!Snap[%%i]!"') do (
echo [REMOVED] %%d
echo [%date% %time%] REMOVED from !Key[%%i]!: %%d >> "%LogFile%"
)
:: Update baseline
copy /y "!CurrSnap!" "!Snap[%%i]!" >nul
)
)
goto :StartupLoop
Why monitor multiple Run keys:
Software (and malware) can register startup entries in several locations. Monitoring only HKCU\...\Run would miss system-wide entries in HKLM\...\Run and one-time entries in RunOnce. This script watches all four common startup locations simultaneously.
How to Avoid Common Errors
Wrong Way: Monitoring an Entire Hive
:: BROKEN: will take minutes, produce massive output, and may crash
reg query HKLM /s > snapshot.txt
The HKLM hive contains millions of entries. Exporting and comparing it is extremely slow and can cause system lag.
Correct Way: Always monitor a specific, narrow key path. If you need to watch a subtree, use Method 2's RegistryTreeChangeEvent for event-driven notification without full exports.
Problem: Permissions on HKLM Keys
The HKEY_LOCAL_MACHINE hive and security-related keys often require administrator privileges to query. reg query fails silently or with a generic error.
Solution: Check the exit code after reg query and report access issues clearly:
reg query "%RegKey%" >nul 2>&1
if errorlevel 1 (
echo [ERROR] Cannot access key. Administrator privileges may be required. >&2
exit /b 1
)
All methods in this guide include this check.
Problem: Binary and Multi-Line Values
Registry values can contain binary data (REG_BINARY), multi-string data (REG_MULTI_SZ), or very long paths. These can produce irregular output from reg query that confuses fc comparison or overflows Batch variables.
Solution: Use file-based comparison (reg query ... > file.txt and fc) rather than storing registry values in Batch variables. This is why all methods in this guide export to files.
Problem: WMI Does Not Report What Changed
WMI registry events (RegistryValueChangeEvent) notify you that a change occurred but do not include the old or new value in the event data.
Solution: After receiving a WMI event, immediately run reg query on the affected key and compare the result against your stored baseline to determine what specifically changed.
Best Practices and Rules
1. Monitor Specific Keys, Not Hives
Always target the narrowest key path that covers your monitoring requirement. Deep recursive exports (/s) on broad keys are slow and produce false positives from unrelated changes.
2. Log Every Detection with Full Context
When a change is detected, log the timestamp, the full key path, and the specific values that changed. Without this detail, an audit trail that says "change detected" is useless for investigation.
3. Use File-Based Comparison, Not Variables
Registry values can be extremely long (thousands of characters for PATH variables, binary blobs for certificates). Always export to files and compare files rather than trying to store values in Batch variables, which are limited to 8,191 characters.
4. Check Permissions at Startup
If your monitor cannot read the target key, it will silently report "no changes" on every iteration, a false negative. Always verify access at startup and fail with a clear error message.
5. Consider Security Implications
Registry monitoring can detect unauthorized changes, but the monitor itself must be protected. If an attacker can stop or modify your monitoring script, the detection is useless. For production security monitoring, use Windows built-in auditing (Group Policy > Audit Object Access) rather than Batch scripts.
Conclusions
Monitoring registry changes turns your Batch script into an auditing tool that detects configuration drift, unauthorized modifications, and software installation side effects. By using snapshot comparison for detailed change tracking or WMI events for immediate notification, you gain visibility into one of the most critical, and least visible, layers of Windows. For production security auditing, complement these scripts with Windows' built-in registry auditing capabilities for tamper-resistant monitoring.