Skip to content

How to Monitor Registry Changes in Batch Script

Learn how to detect registry modifications in Windows using Batch scripts with snapshot comparison or PowerShell WMI events for auditing software changes, security policy violations, or configuration drift.

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.

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:

  1. reg query exports all values under the specified key to a text file.
  2. fc compares the previous and current exports byte-by-byte.
  3. If the files differ, findstr /v /x /g: cross-references the two files to identify specifically which lines were added, changed, or removed.
  4. 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%


$hive = $env:Hive
$keyPath = $env:KeyPath
$logFile = $env:LogFile
$displayKey = $env:DisplayKey

if ($hive -eq "HKEY_CURRENT_USER") {
    $sid =[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
    $hive = "HKEY_USERS"
    $keyPath = "$sid\\$keyPath"
}

$query = "SELECT * FROM RegistryKeyChangeEvent WHERE Hive='$hive' AND KeyPath='$keyPath'"

try {
    $watcher = New-Object System.Management.ManagementEventWatcher($query)
    
    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
        
        "[$ts] Change detected in: $displayKey" | Out-File -FilePath $logFile -Append
        
        $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\\Microsoft not Software\Microsoft.
  • Hive names must be full: Use HKEY_CURRENT_USER not HKCU, and HKEY_LOCAL_MACHINE not HKLM. 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_MACHINE keys 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.

Navigation