Documentation
regx is a single-file Windows Registry CLI for standard users. Every command below runs
without elevation. Where a command physically cannot succeed without admin rights, it says so and
exits with a specific code rather than failing obscurely.
Install
Once a release is available, there is nothing to install: put regx.exe
anywhere you can write and run it. It creates no registry entries, no service,
no scheduled task and no %APPDATA% folder.
The first binary release has not been published yet. Build from source below; do not download executables claiming to be an official release until they appear on the GitHub releases page.
# Verify it runs and see what the environment allows
> .\regx.exe --self-check
regx self-check
[ok ] process 64-bit process. HKLM\SOFTWARE resolves to the 64-bit view.
[ok ] elevation not elevated (medium integrity), as intended
[ok ] hkcu HKCU\Software is writable - the redirection target is usable
A file downloaded with a browser carries a Mark-of-the-Web stream that triggers SmartScreen.
--self-check reports it; clear it with Unblock-File .\regx.exe.
Build from source
> cargo build --release # -> target\release\regx.exe
Requires a Rust MSVC toolchain. The build embeds the asInvoker manifest into the PE and
statically links the CRT, so the result needs no Visual C++ redistributable.
Release maintainers run both check_release_identity.py --self-test and
check_release_assets.py --self-test. Before publication, the first requires one
exact tag/Cargo/date/changelog-notes identity and emits the notes GitHub consumes; the second
checks exact asset coverage, checksums, PE architecture, elevation manifest, static CRT,
CycloneDX identity/version, and the strict binary-size contract.
Generate Unix manual pages
Maintainers packaging regx for WSL or a Unix documentation system can
generate section-1 pages for the root and every nested command from the
same Clap metadata. The renderer is not linked into regx.exe.
$ cargo run --example generate-man -- target/man
Benchmark large inputs
The development harness creates synthetic .reg,
Registry.pol, and private application-hive workloads under
target/benchmark-large. It measures the release executable
end to end and reports throughput, operations per second, and peak
working set without touching the user's registry.
$ cargo run --release --example benchmark-large -- target/release/regx.exe 5000
Fuzz parser inputs
Three development-only libFuzzer targets exercise raw .reg
bytes, XML, and every forced input dialect. Checked-in seeds and a
deterministic 10,000-case mutation smoke are tested normally; the
GitHub workflow is configured for short AddressSanitizer campaigns on
parser changes and each week. Setup and crash-triage instructions live in
fuzz/README.md.
Quick start
# 1. Lint and repair a .reg file you found online
regx validate app.reg --fix --backup
# 2. See where each key would land, without writing anything
regx convert app.reg --redirect auto
# 3. Check you can actually write to the destination
regx probe "HKCU\Software\Acme"
# 4. Apply it. An undo file is written before anything changes.
regx import app.reg
# 5. Changed your mind
regx undo app.undo.reg
Stream data through stdin or Windows Named Pipes
Commands that read registry data accept - once for standard input and
pipe:NAME for one-shot Windows IPC. Both avoid temporary files while
preserving content-based format detection.
> Get-Content app.reg -Raw | regx inspect -
> Get-Content policy.json -Raw | regx convert - --from json --redirect off
A Named Pipe producer must use byte mode, write one complete registry-data document, and
close the pipe. regx waits up to five seconds and accepts at most 64 MiB. The native
\\.\pipe\NAME spelling is also supported.
> $producer = Start-Job {
$p = [IO.Pipes.NamedPipeServerStream]::new("regx-input",
[IO.Pipes.PipeDirection]::Out, 1, [IO.Pipes.PipeTransmissionMode]::Byte)
try {
$p.WaitForConnection()
$bytes = [Text.Encoding]::UTF8.GetBytes((Get-Content policy.json -Raw))
$p.Write($bytes, 0, $bytes.Length); $p.Flush()
} finally { $p.Dispose() }
}
> regx inspect pipe:regx-input --from json --output json
> Wait-Job $producer | Receive-Job
Stream import and sync require -y unless they are
dry runs. validate --fix requires --out, and saved plans reject
streams because a closed source cannot be re-verified.
Shell completion
Generate completion from the executable's own Clap command metadata. The output therefore includes every shipped command and flag without a separately maintained script.
> regx completions powershell | Out-String | Invoke-Expression
# Also supported: bash, elvish, fish, zsh
The command writes only to standard output. Redirect it to the shell-specific completion directory or add the invocation to your profile when you want it loaded permanently.
Global flags
These apply to every command.
| Flag | Effect |
|---|---|
--dry-run | Perform every read — so permission problems still surface — but skip all writes. |
-y, --yes | Skip confirmation prompts. |
--output text|json | Machine-readable output for pipelines. |
--view 64|32|both | Choose the WOW64 registry view explicitly. Read-only live commands report both views separately. Mutations, batch operations, backup/restore, copy/move, saved plans, and pruning sync keep independent per-view state, artifacts, undo and cross-view rollback. Commands that do not access a registry view simply have no view-specific effect. |
--log-level | error, warn, info (default) or debug. |
--self-check | Report what AppLocker, SRP, WDAC and the process token do to this binary, then continue or exit. |
Exit codes
Stable across releases, so a script can branch on them.
| Code | Meaning | Typical cause |
|---|---|---|
0 | Success | — |
2 | Usage error | Unknown flag, missing argument. |
3 | Parse error | The .reg file has invalid syntax. |
4 | Access denied | The key exists but this token cannot write to it. |
5 | Partial success | Some keys were skipped or some subkeys were unreadable. |
6 | Redirection refused | A key had no per-user equivalent and --on-refuse fail was set. |
7 | File I/O error | Could not read or write a file on disk. |
8 | Not found | The key or value does not exist. |
validate
Parse and lint one or more .reg files without touching the
registry. Use inspect for structural and fidelity
validation of any supported input format.
regx validate <FILE...> [--strict] [--fix [-o FILE] [--backup]]
What --fix repairs
Safe repairs are unambiguously what the author meant. Lossy repairs change bytes — they are still applied, because the file is already broken, but they are labelled so you can judge.
| Defect | Repair | Class |
|---|---|---|
hex(1)/hex(2)/hex(6) missing NUL terminator | Append 00,00 | safe |
hex(7) missing double NUL | Append until the list terminates | safe |
Trailing whitespace after a \ continuation | Removed — regedit stops folding there and drops the rest of the payload | safe |
| Control characters in a key path or value name | Stripped | safe |
| Duplicate key blocks | Coalesced, last write wins | safe / lossy |
| Odd-length UTF-16 payload | Padded with one NUL byte | lossy |
hex(4)/hex(b) of the wrong length | Reported, never guessed | not fixed |
--fix refuses syntax errors and accepts exactly one input
per invocation. This prevents a later invalid file from leaving an
earlier file repaired as a partial multi-file operation.
With --output json, a requested repair includes
repairedData: the complete lossless registry-data object
that would be or was written. It is available during
--dry-run, preserves numeric type IDs and raw bytes, and
is null for read-only validation or syntax-error refusal.
A written repair additionally reports its exact output path, byte
length and SHA-256. In-place --backup reports the same
evidence independently and no longer contaminates JSON stdout with a
text-only status line.
convert
Transform a .reg file offline. This command never touches the registry, so it is the safe way to preview a redirection.
regx convert <FILE> [-o FILE] [--to reg|json|csv|pol] [--redirect MODE] [--conflicts last-wins|error] [--min-confidence LEVEL] [--reg4]
With no -o, the result is written to standard output.
--conflicts error rejects semantic drift inside the source
or introduced by Smart Redirection before writing either destination.
--reg4 emits the legacy ANSI
REGEDIT4 dialect instead of UTF-16 Version 5.00. File and stdout bytes use the machine's
active ANSI codepage and refuses best-fit substitution; use V5 when text is not representable.
--to json emits the explicit {"keys": [...]} schema and
--to csv emits spreadsheet-ready rows. Both preserve unknown registry types and malformed
payloads as a numeric type id plus raw hexadecimal bytes, so converting back to .reg is
byte-exact rather than based on display text.
--to pol writes a version-1 binary Registry.pol for one implicit
HKCU or HKLM policy root. Empty keys, strings, DWORDs, MS-GPREG-defined raw types,
named-value deletion and subtree deletion round-trip exactly. Mixed hives,
implicit-root/default-value mutation, undefined types and record payloads above 65,535
bytes are refused before output because the protocol does not define them.
Status and query output selected with --output json has a
versioned command-to-schema catalog.
Its command map identifies the correct definition for each machine-readable operation.
watch validates one event per line. Data/script producers reject the ambiguous global
flag: use convert --to json; merge and completions retain
their native .reg and shell-source streams.
merge
Combine any supported registry-data formats into one .reg,
JSON, CSV, or binary Registry.pol output.
Each input is detected independently; policy-reader options such as
--pol-root, --inf-language, and
--admx-state are available. Semantic losses fail closed
before output. Duplicate keys are folded case-insensitively with
last-write-wins, and every value or key-state conflict is reported. For unattended
pipelines, --conflicts error refuses the merge before
creating output if inputs assign different data to the same value or
disagree on whether a key is created or deleted.
regx merge <FILE> <FILE...> [--to reg|json|csv|pol] [--conflicts last-wins|error] [-o FILE] [--reg4]
import, undo and sync
Merge registry-data files into the live registry. Both capture a complete undo snapshot before making any change.
regx import <FILE...> [--redirect MODE] [--conflicts last-wins|error] [--value GLOB] [--exclude-value GLOB] [--backup FILE | --no-backup]
regx undo <FILE> [--backup REDO_FILE]
regx sync <FILE> [--redirect MODE] [--conflicts last-wins|error] [--prune [--prune-keys]] [--backup FILE | --no-backup]
Use --conflicts error for fail-closed automation. It rejects
conflicting value data and key create/delete state found either inside
one input or after combining and redirecting inputs. Rejection occurs
before registry reads, undo files, audit records, or confirmation.
Repeat --value GLOB and --exclude-value GLOB to import selected value names;
matching is case-insensitive and @ denotes the default value. Once selection is active,
empty-key creates and whole-key deletes are omitted, so value scope cannot silently become key scope.
The undo snapshot
The registry offers no transaction, so a failed merge would otherwise leave half-applied state.
Before writing, regx computes the inverse of the pending change and saves it as an ordinary
.reg file beside the input. The default filename begins with the input stem and
includes PID, nanosecond time, and an atomic sequence, so concurrent operations do not
overwrite one another. Use --backup FILE when a script needs a stable explicit path.
[-KEY]that exists → the whole subtree is exported, so undo recreates it- Key exists, value exists → the current data is recorded
- Key exists, value absent →
"name"=-is recorded - Key does not exist →
[-TOPMOST_MISSING_ANCESTOR], not[-KEY]— deleting only the leaf would leave the intermediate keys behind as empty shells
Restores are ordered before removals. If any key cannot be read, the operation is refused rather than
trusting an incomplete inverse. If a later mutation fails after earlier writes succeeded, regx
automatically applies the snapshot and reports both phases. --no-backup explicitly
disables the undo file and automatic rollback.
JSON results identify each per-view undo artifact and include its exact
undoBytes and undoSha256. Dry-run and
--no-backup use null evidence.
Apply a saved inverse with regx undo FILE. Redirection is always disabled so the
recorded paths are restored exactly, and regx captures a fresh redo snapshot before changing
anything. For a dual-view bundle, pass its base or either generated member together with
--view both; both .32.reg and .64.reg members must exist
and are restored as one cross-view atomic operation. JSON seals each persisted redo with exact
redoBytes and redoSha256; dry-run uses null evidence.
sync --prune
Makes the apply idempotent: any live value under a declared key that the file does not mention becomes
an explicit delete. Use it when the .reg file is meant to be the complete desired state.
Add --prune-keys to treat declared paths as the complete desired tree and recursively
remove topmost branches that are not represented. This stronger mode requires --prune,
reads every affected subtree before writing, refuses ACL gaps, and includes every generated delete in
policy checks, the undo snapshot, audit events, and automatic rollback. Use the same flags with
plan to preview the exact deletions.
export
Write a live key directly to .reg, JSON, CSV, or Registry.pol. REG output remains byte-compatible with regedit's own output so diffs are meaningful.
regx export <KEY> [-o FILE] [--to reg|json|csv|pol] [--root-as KEY] [--no-recursive] [--include GLOB] [--exclude GLOB] [--value GLOB] [--exclude-value GLOB] [--reg4]
A denied subkey never aborts the export. Partial export of your own hive is normal — Group-Policy-locked
policy keys and Protected subtrees are common — so skips are listed and the command exits
with code 5.
Key-path filters use repeatable --include/--exclude globs,
while value-name filters use the same --value/--exclude-value rules as import.
Patterns match portable paths after --root-as; * stays within one path
component and ** crosses separators.
Export is recursive by default; --no-recursive keeps only the requested key.
No match exits 8 and does not create the requested output file. Status JSON records
the effective scope, format and filters, reports counts after filtering,
and seals every created artifact with its exact bytes and
lowercase sha256. Dry-run and inline-data views report
null evidence because no file exists.
--root-as KEY replaces the requested source key with a validated destination
and preserves relative descendants in every output format, enabling explicit HKLM/HKU-to-HKCU
migration artifacts without mutating the source.
--view both -o NAME.EXT keeps WOW64 views distinct as
NAME.32.EXT and NAME.64.EXT. Without -o, add
--output json to receive both datasets and per-view failures in one document;
plain-text stdout is refused because concatenating two registry files would not be safely re-importable.
query, ls, stats, fingerprint, set, delete
regx query <KEY> [-v NAME] [-r]
regx ls <KEY> [-r] [--include GLOB] [--exclude GLOB] [--limit N] [--computer COMPUTER]
regx stats <SOURCE> [--computer COMPUTER] [--root-as KEY] [--view native|32|64|both] [--include GLOB] [--exclude GLOB] [--value GLOB] [--exclude-value GLOB]
regx fingerprint <SOURCE> [--computer COMPUTER] [--root-as KEY] [--view native|32|64|both] [--expect SHA256] [--include GLOB] [--value GLOB]
regx set <KEY> -v NAME -t TYPE -d DATA [--redirect MODE] [--backup FILE]
regx delete <KEY> [-v NAME] [-r] [--backup FILE]
ls lists immediate child keys without opening or printing their values;
-r walks all descendants. It supports independent WOW64 views and read-only
remote HKLM/HKU access. Repeatable --include/--exclude globs scope
canonical paths, while --limit (default 1000) bounds matches per view and reports
truncated. Skipped ACL paths and per-view failures are preserved in strict JSON.
fingerprint computes canonical SHA-256 v1 over exact paths, names,
deletion state, numeric types, and raw bytes without printing value data. Reordering
equivalent source records leaves the digest unchanged; any exact registry-state change
changes it. Files, stdin, live/remote keys, and independent WOW64 views are supported.
--expect SHA256 exits 5 on drift for files and one selected view.
A dual-view gate requires both --expect-32 and --expect-64,
preventing a partially checked pair.
Repeatable key --include/--exclude and value
--value/--exclude-value globs restrict the exact state covered.
JSON echoes the scope and selected counts; no match exits 8 with
matched:false, never a false successful empty hash.
For migrations, --root-as KEY rebases a live/remote subtree before
hashing; offline hive fingerprint rebases the mounted hive root and produces the
same digest as an equivalently rebased export. File inputs reject ambiguous rebasing.
stats summarizes any supported file, stdin, or live/remote key without
printing value names or payloads. It reports effective key and value counts, registry
types, raw payload bytes, delete operations, maximum depth, conflicts, and completeness;
dual-view results remain separate.
Repeatable key and value include/exclude globs scope the metrics exactly like
fingerprint/export. JSON echoes that scope and matched; no match exits 8.
For migration reports, --root-as KEY rebases a live/remote subtree
before filtering and keeps maximum depth relative to the mapped requested root.
Offline hive stats maps the mounted hive root with the same semantics. JSON records
the canonical rootAs; file inputs reject ambiguous rebasing.
delete without -v removes the key and its subkeys, so it requires -r
as an explicit acknowledgement.
Both mutations capture a complete inverse for every selected registry view before asking for
confirmation. After acceptance, the inverse is written to a temporary .reg file;
--backup FILE chooses a durable path, and --view both produces
FILE.32.reg and FILE.64.reg. Cancelling writes neither registry state
nor the requested backup. Temporary names include the process, nanosecond time, and an atomic
sequence so concurrent commands cannot reuse one undo path. The JSON result includes each
view's exact undo path.
Add --computer COMPUTER to query for read-only remote HKLM/HKU access.
The same option is available on export and live-key search. It uses
Windows RegConnectRegistryW; the Remote Registry service, firewall path, and remote
ACLs must already permit the connection. Mutation commands intentionally have no remote option.
With --output json, every query value retains the human-readable
type/data preview and adds an exact object.
That object preserves typed strings and DWORDs, or the numeric registry
typeId plus raw bytes for every other type. Live, remote, dual-view,
and offline-hive queries therefore share one lossless automation contract.
copy & move
regx copy <SOURCE> <DEST> [--overwrite] [--backup FILE]
regx move <SOURCE> <DEST> [--overwrite] [--backup FILE]
regx copy-value <SOURCE_KEY> <VALUE> <DEST_KEY> [--dest-value NAME] [--save-plan FILE]
regx move-value <SOURCE_KEY> <VALUE> <DEST_KEY> [--dest-value NAME] [--save-plan FILE]
regx copy <SOURCE> <DEST> --save-plan preview.json
regx apply-copy-plan preview.json -y
Both commands copy the complete readable subtree and write one undo snapshot before changing
anything. A move is two-phase: the source is deleted only after every destination write succeeds.
Existing destinations are refused unless --overwrite is supplied; overwrite merges and
leaves unrelated destination values intact. An unreadable source subkey or incomplete rollback
snapshot aborts the operation instead of risking silent data loss. A failure in either phase
automatically applies the combined snapshot. With --view both, both source and
destination pairs are preflighted and snapshotted before mutation, separate
.32.reg/.64.reg undo files are written, and a later-view failure rolls
back every earlier touched view.
--save-plan writes a
digest-bound collision preview instead of mutating.
apply-copy-plan refuses source or destination drift and emits a
versioned result.
In dual-view mode, the preview is stored as a .32.json/.64.json
pair; apply verifies both artifacts and both live snapshots before writing either view.
JSON creation results seal every persisted plan independently with
planBytes and planSha256, including value plans and both
members of a dual-view pair.
Direct mutation and apply-copy-plan results seal every persisted undo
with backupBytes and backupSha256; dry-run uses null evidence.
For remote-to-local migration, add --source-computer COMPUTER to
copy. The source computer and subtree are bound into saved previews, while every write
remains local. move intentionally exposes no remote-source option.
copy-value and move-value operate on one value while preserving
every sibling value and subkey. Use @ for the unnamed default value and
--dest-value to rename it. They use the same two-phase copy/delete,
combined undo snapshot, audit, dry-run, JSON, remote-copy, and cross-view rollback
guarantees as subtree operations. Their --save-plan artifacts bind the exact
value payload and both names; version 1 subtree plans remain readable.
backup & restore
regx backup <KEY> <HIVEFILE> [--computer HOST]
regx restore <HIVEFILE> <DEST> [--overwrite] [--backup UNDO.reg]
backup creates a genuine native regf application hive without elevation.
It preserves keys, empty keys, registry types, and raw value bytes. The file can be inspected or edited
with the existing hive commands. --view both preflights both WOW64 views
before writing NAME.32.hiv and NAME.64.hiv; if either write fails, neither
artifact is kept. --computer HOST reads remote HKLM/HKU while writing only the
local hive artifact; no remote mutation API is used. Successful JSON
output records the exact bytes and lowercase
sha256 of every created hive. Both are explicitly
null during dry-run because no artifact exists yet.
restore rebases the saved root under an explicit live destination. It refuses an existing
destination unless --overwrite is supplied, and routes the mutation through administrative
policy, audit, a complete undo snapshot, and automatic rollback. In dual-view mode it reads the
.32.hiv/.64.hiv pair, writes separate undo files, and rolls back every touched
view if either restore fails. JSON seals every persisted undo snapshot
with undoBytes and undoSha256, independently
per view; dry-run uses null evidence because no undo file is written.
Application hives do not preserve per-key ACLs, key classes, or last-write timestamps. Windows'
full-fidelity RegSaveKeyEx requires SeBackupPrivilege, so claiming those
properties would conflict with regx's standard-user contract.
probe
Answers the only question that matters before an import: can this user actually write here?
It really opens the key rather than inferring from the path, because an ACL on a single subkey can deny
a standard user even inside their own HKCU.
> regx probe "HKLM\SOFTWARE\Microsoft"
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft
exists true
readable true
writable false
creatable false
probe has no side effects: when the key does not exist it walks up to the nearest existing
ancestor and tests write access there instead of creating a scratch key.
--computer HOST performs the same capability check against remote HKLM/HKU;
it only opens handles with specific access masks and never creates or changes remote state.
permissions
Inspect the key's real security descriptor without changing it. The result includes owner SID, DACL inheritance/protection, portable SDDL, and the current token's effective access for each individual registry right.
> regx permissions "HKCU\Software\Acme"
> regx permissions "HKLM\Software\Acme" --view both --output json
> regx permissions "HKCU\Software\Acme" --compare "HKLM\Software\Acme" --exit-code
Effective query, enumerate, notify, set-value, create-subkey, and delete rights are measured by
opening the key with each specific access mask—not inferred from whether the path begins with HKCU
or HKLM. --view both reports the 32-bit and 64-bit views separately.
--compare OTHER lists owner, inheritance, SDDL and effective-right differences;
--exit-code returns 5 when drift exists.
Use --computer HOST for the first key and
--compare-computer HOST for the comparison key, independently, to audit
local-to-remote or remote-to-remote ACL drift without enabling remote writes.
hive — offline hive files
RegLoadKey, which reg load and regedit's Load Hive both call, requires
SeRestorePrivilege. RegLoadAppKeyW does not: it mounts the hive into a private,
unnamed slot that only the calling process can see, so there is no global namespace entry to protect and
no privilege to check.
Why mount and unmount are not separate commands
The handle is process-scoped. Closing it — including at process exit — unloads the hive. So this cannot work:
regx hive mount NTUSER.DAT --as my_hive # process 1 exits -> hive unloaded
regx hive set my_hive\Software\App ... # process 2: nothing is mounted
regx hive unmount my_hive # process 3: nothing to unmount
There is no supported workaround. The handle cannot be published to the registry namespace — that is exactly what the privilege check guards — and inheriting it into another process would require the mounting process to stay alive, i.e. a resident daemon, which defeats "portable, no install". Mount, operate and unmount therefore happen inside one process.
# Several operations under a single mount
regx hive "C:\path\MyApp.hive" --create exec \
-c "set Software\MyApp -v License -d OK" \
-c "set Software\MyApp -v Seats -t REG_DWORD -d 25" \
-c "query Software\MyApp -r"
# A single operation needs no exec
regx hive "C:\path\MyApp.hive" export Software --to json -o offline.json --root-as "HKEY_USERS\OFFLINE"
regx hive "C:\path\MyApp.hive" probe Software\MyApp
regx hive "C:\path\MyApp.hive" permissions Software\MyApp --output json
regx hive "C:\path\MyApp.hive" -y batch changes.json --strip-root HKCU --backup hive-undo.reg
regx hive "C:\path\MyApp.hive" search Software license --field name
regx hive "C:\path\MyApp.hive" diff Software\MyApp desired.reg --strip-root HKCU --exit-code --to json -o drift.json
regx hive "C:\path\MyApp.hive" -y sync desired.reg --strip-root HKCU --conflicts error --prune --prune-keys --backup sync.undo.reg
regx hive "C:\path\MyApp.hive" -y copy Software\MyApp Software\MyApp.Backup --backup copy.undo.reg
regx hive "C:\path\MyApp.hive" -y move Software\MyApp.Backup Software\MyApp.Renamed
regx hive "C:\path\MyApp.hive" -y undo copy.undo.reg --backup copy.redo.reg
Operations
| Operation | Description |
|---|---|
info | Size, regf signature, and whether the hive can be mounted read-only or read/write. Run this first. |
ls | List subkeys; add -r for descendants. Repeatable --include/--exclude globs scope relative hive paths, and --limit bounds matching output with explicit truncation status. |
stats | Summarize keys, values, types, payload bytes, deletes, depth, conflicts, and completeness below a subkey without printing value payloads; key/value globs scope managed state, and --root-as normalizes migration roots before filtering while preserving relative depth. |
fingerprint | Compute the canonical SHA-256 v1 for exact subtree state without printing value names or payloads; --expect exits 5 on drift, key/value globs scope state, and --root-as normalizes migration roots. |
query | Print values under a subkey. |
probe | Test whether a subkey exists and can be read, changed, or created without modifying the hive. |
permissions | Report owner SID, inheritance, SDDL, and effective query/enumerate/notify/set/create/delete access for a subkey. |
search | Search keys, value names, types, or data below a subkey using bounded substring, glob, or Unicode regex matching, with path/value include/exclude filters and result limits. |
diff | Compare a subtree directly with any supported registry-data file, optionally write the applicable drift patch, scope key paths or value names, summarize, or return exit 5 when different. Value selection omits structural key changes so unselected siblings cannot be deleted. |
set | Write one value, with a persisted inverse selectable through --backup. |
delete | Delete a subkey or a single value, with a persisted inverse selectable through --backup. |
copy / move | Copy, move, or rename a complete subtree. Existing destinations require --overwrite; recursive self-destinations are rejected, partial operations roll back, and --backup persists the inverse. |
copy-value / move-value | Copy, move, or rename one value without copying sibling values or subkeys. --backup persists the inverse. |
import | Merge any supported registry-data file in. Content-first detection and the shared POL/INF/ADMX selectors apply; fidelity losses fail before snapshotting. --strip-root removes the mount-point prefix; --conflicts error rejects duplicate semantic drift before snapshot or mutation; --backup selects the undo artifact. |
undo | Restore a generated inverse with its HKCU mount label removed automatically, while persisting a fresh redo snapshot before any hive change. |
sync | Reconcile any supported registry-data file into the hive through the shared lossless reader pipeline. --conflicts error fails closed after root stripping; --prune removes undeclared values, --prune-keys removes unrepresented subtrees, and --backup selects the complete inverse. |
batch | Apply the published v1 JSON manifest atomically under one mount. Every operation is re-rooted and policy-checked before any write, one shared inverse is persisted, and a mid-batch failure rolls back all earlier operations. |
export | Write part of the hive as REG, JSON, CSV, or Registry.pol. --root-as assigns every exported path a real registry key because an application hive has no permanent namespace; --no-recursive, key-path --include/--exclude, and value-name --value/--exclude-value globs scope the artifact. Filters match the rebased portable paths. Empty selections create no file, and states Registry.pol cannot encode are refused before output. |
exec | Run several operations under one mount, via repeated -c or a --script file. |
What you can realistically open
A private application-hive handle has one namespace, not separate WOW64 views. Hive
operations therefore reject --view 32, --view 64, and
--view both instead of silently ignoring them.
Every mutation prompts unless -y is allowed. Administrative
RequireConfirm disables that bypass for set, delete, import, sync, subtree and
value copy/move alike. Every single-operation mutation takes a complete inverse before the
prompt, persists it only after acceptance, and automatically applies it after a partial failure.
Use --backup FILE to choose the path, then reapply it with
hive HIVEFILE undo FILE -y. Undo automatically removes the snapshot's HKCU
mount label and captures another reversible redo snapshot. Ordinary mutations expose
undo in JSON; this command exposes the new artifact unambiguously as redo,
with exact byte length and SHA-256 evidence for either artifact. Dry-run uses null evidence.
Both forms also include the apply and rollback reports. Default temporary undo names use the same collision-resistant
allocator; repeated mutations inside one hive exec receive distinct paths.
Root deletion is rejected during preflight, including when it is hidden inside an import,
sync, or batch manifest.
Write access to the file is still required, and a hive already mounted by the OS is held
exclusively. Realistic targets: a logged-off secondary profile, a copy of a hive, an
application's private hive, or a hive on a mounted backup or VHD. A logged-on user's
NTUSER.DAT fails with ERROR_SHARING_VIOLATION by design —
hive info reports this before you commit to a write.
inspect
Report what a file is and what it contains, without applying any of it. Use it on anything you did not write yourself before deciding whether to import.
> regx inspect "C:\Windows\System32\GroupPolicy\Machine\Registry.pol"
format pol
key blocks 2 (0 whole-key delete(s))
values 6
hives HKEY_LOCAL_MACHINE
note 5 policy record(s)
note policy paths rooted at HKEY_LOCAL_MACHINE (a .pol stores no hive)
1 key(s) have no per-user equivalent; `regx convert` shows which
Text and JSON output include the detected source encoding; registry
files also report REGEDIT4 versus
Windows Registry Editor Version 5.00.
Duplicate semantic drift exits 5. Text identifies every
conflicting path/value and its before/after preview; JSON exposes the
strict conflicts[] objects with source line numbers plus
oldExact/newExact typed/raw value payloads so
automation can repair the input before selecting
--conflicts error.
Whole-key conflicts use null exact payloads because no individual value exists.
Each JSON report additionally embeds data, the complete
lossless parsed registry-data model. It remains inspectable for an
incomplete or conflicting source even when convert
correctly refuses to emit that partial model as a safe artifact.
regx formats lists every supported format and how each is detected.
discover
Enterprise executables find their own configuration by anchoring on
GetModuleFileNameW(NULL) — the real path of the running module. It is used
rather than argv[0] because a parent process chooses argv[0] and
can point it anywhere. Strip the extension, append .ini, and that is the classic
sidecar; .NET reaches MyApp.exe.config the same way. Around that, products layer
a search order.
discover reproduces that search, reports which rung each hit came from, and
flags the rungs that are load-bearing security bugs.
> regx discover "C:\Program Files\Acme\updater.exe" --strict
anchor C:\Program Files\Acme
stem updater
4 companion file(s), in search order:
[3] beside the executable C:\Program Files\Acme\updater.ini
ini 84 bytes
[9] current directory D:\Shared\updater.ini
ini 74 bytes
RISK CurrentDirectory: anyone who can write there controls this configuration
The search order
| Rank | Origin | Notes |
|---|---|---|
| 1 | explicit path | Passing a config file rather than an executable anchors on its directory and records the file here. |
| 2 | environment variable | <STEM>_CONFIG, <STEM>_HOME, <STEM>_INI. |
| 3 | beside the executable | The sidecar, plus the .exe.<ext> convention. |
| 4–6 | %LOCALAPPDATA%, %APPDATA%, %PROGRAMDATA% | Under a \<stem>\ subdirectory. |
| 7 | registry pointer | Software\<stem> ConfigPath. Opt in with --registry-pointer. |
| 8 | Group Policy caches | Registry.pol and PolicyDefinitions. Opt in with --policy. |
| 9 | current directory | Always probed so its risk can be reported. Never trusted. |
| 10 | %WINDIR% | Where GetPrivateProfileString silently resolves a bare file name. |
Risks reported
| Risk | Why it matters |
|---|---|
CurrentDirectory | Anyone who can write to a directory a user launches from controls that configuration. This is config planting — the same shape as DLL planting. |
UserWritable | The file sits somewhere this user can write, while the executable's own directory is protected. A lower-privileged location overriding a higher-privileged one. |
ReparsePoint | Reached through a symlink or junction; the real target may be elsewhere entirely. |
EscapesAnchor | A sidecar whose resolved path leaves the anchor directory got there through a link. |
NetworkPath | UNC or a mapped drive: availability and integrity are not local. |
WindowsFallback | The %WINDIR% resolution of the profile-string APIs, which plenty of legacy code hits without intending to. |
ShortNameAlias | Matched only after 8.3 expansion, so path comparisons elsewhere in the system may not agree that these are the same file. |
Directory writability is asked of the OS, not inferred from the path: the
directory is opened for FILE_ADD_FILE, which is an access check with no side
effect — the same principle as probe. And
discover reports what an application would find; it does not claim to
know which rungs a given product actually implements. Confirm before trusting a hit.
With --output json, the versioned report includes the
resolved executable and anchor, enabled discovery controls, notes,
every probed-but-absent candidate in searched, and the
aggregate risky count. Every hit keeps both its candidate
path and canonical resolvedPath, alongside
stable risk names and structured riskDetails
explanations. JSON always retains that full probe trail;
--verbose only expands text output.
--strict exits 5 if any hit carries a risk, so
a deployment check can gate on it.
diff
Compare any two sources and emit the patch between them. Each side is either a file in any supported format or a live registry key, so file-to-file, file-to-live and live-to-live all work from the same argument positions.
> regx diff baseline.reg "HKCU\Software\Acme" --exit-code
~ HKEY_CURRENT_USER\Software\Acme\Channel
- stable
+ beta
+ HKEY_CURRENT_USER\Software\Acme\NewFlag = 0x00000001 (1)
- HKEY_CURRENT_USER\Software\Acme\Retries = 0x00000003 (3)
1 added, 1 modified, 1 removed
The patch written by -o turns A into B. So a drift report is
also the fix, and swapping the arguments produces the rollback.
--exit-code exits 5 when the two sides differ, which makes it
usable as a deployment gate. If either input contains parse losses or conflicting duplicate
assignments, the visible comparison is still reported as incomplete and exits 5,
but -o refuses to write a potentially unsafe patch.
Select --to reg|json|csv|pol to write that patch directly in any
round-trip registry-data format; an unrepresentable Registry.pol patch fails closed.
JSON status seals each created patch with exact bytes and
lowercase sha256. Dry-run, omitted and incomplete-source
patches carry explicit null evidence.
--map-a FROM=TO and --map-b FROM=TO rebase a complete
input subtree before comparison. Use this for migrations where equivalent settings live
under different roots, such as HKLM to HKCU. Every key on that side must be beneath
FROM; invalid or partial mappings are rejected. Counts, filters, JSON changes,
and the generated A-to-B patch all use the mapped destination path.
Repeat --value GLOB and --exclude-value GLOB to scope
comparison to value names; use @ for the default value. Activating
value selection removes structural key changes from the diff. If the target lacks
an entire key, the patch therefore emits only selected value deletions and cannot
remove unselected sibling values.
JSON value changes retain the compatible left/right
previews and add leftExact/rightExact. A present side
carries its typed value or numeric type ID and raw bytes; an absent side is null.
The same lossless change contract is used by single-view, dual-view, remote, and
offline-hive diff.
Repeat case-insensitive glob --include/--exclude options to scope a large
tree. --summary-only emits counts without individual changes. The same filtered diff
drives counts, exit status, JSON, and -o, so a summary-only patch remains complete for
the selected scope.
If either side is live, --view both compares the file/live or live/live pair
independently in the 32-bit and 64-bit views. -o PATCH.json --to json writes
PATCH.32.json and PATCH.64.json; JSON retains per-view counts,
completeness, changes, patch path, write status and failures. The paired output is fail-closed:
neither patch is written if either view fails or is incomplete. A stdin side is read once and reused
for both views.
--computer-a HOST and --computer-b HOST independently read a live
HKLM/HKU side through Windows Remote Registry. This supports remote-to-file,
remote-to-local, and remote-to-remote drift checks without enabling remote mutation; either
option is rejected when its corresponding side is a file, stdin, HKCU, or another unsupported root.
Comparison is case-insensitive on key paths and value names, because the registry is, but
byte-exact on data: a REG_SZ and a REG_EXPAND_SZ holding the same
characters are a difference, not a match, since one is expanded by the consuming application
and the other is not.
search
Search any supported file, stdin, or a live registry subtree with the same command. Matching supports Unicode-aware substring, glob, or bounded regular-expression queries over key paths, value names, registry types and full data payloads.
> regx search "HKCU\Software" example.test --field data --limit 20
> regx search policy.json REG_DWORD --field type --output json
> regx search app.reg "^Server(Name|Backup)$" --match regex --include "HKCU\Software\Acme\**" --exclude "**\Cache" --value "Server*"
> Get-Content app.reg -Raw | regx search - ServerName --field name
Repeat --field key|name|type|data to select fields; omit it to search all four.
Use --match glob|regex for patterns, --case-sensitive for exact case,
and repeat the always-glob --include/--exclude path filters.
A single * stays within one key component; ** crosses subkeys.
Repeat --value GLOB/--exclude-value GLOB to scope value matches
case-insensitively; @ denotes the default value. Activating value scope omits
key-only matches so a value-bounded search cannot silently widen to key scope. The same
contract applies to offline hive search.
No matches exits with code 8. JSON output reports whether the result was truncated by
--limit. If an ACL blocks any live subkey, the result is marked
incomplete and exits 5 rather than claiming a complete search.
Parse losses or conflicting duplicate assignments in a file source have the same
incomplete/exit-5 semantics.
Each JSON value match includes an exact registry-value object with
typed data or numeric type ID and raw bytes; key-path matches use
exact: null. The display preview is retained for compatibility.
With a live source, --view both reports independent 32-bit and 64-bit matches,
truncation and ACL completeness; --limit applies per view.
watch
Wait for native Windows registry notifications without polling, then compare snapshots to identify
the keys and values that changed. JSON is emitted one object per line for streaming automation.
Every value change includes lossless leftExact/rightExact objects;
added or removed sides use null, while present sides preserve typed values or numeric
type ID and raw bytes.
> regx watch "HKCU\Software\Acme" --count 10 --timeout 300 --output json
> regx watch "HKCU\Environment" --no-recursive
A zero timeout waits indefinitely. A bounded timeout with no change returns success and emits
timedOut: true. Recursive watch is the default; unreadable descendants make the baseline
or result incomplete and exit 5. --view both arms native Win32 events for
the 32-bit and 64-bit keys and waits on both without polling. Each event identifies
triggeredView and includes separate snapshot diffs for both views. Carrying the
before/after payload in the notification avoids a race-prone follow-up query.
plan
Resolve an import or sync before mutation. Unlike a summary-only dry run, the plan lists every key/value
operation with before and after state, Smart Redirection outcomes, administrative-policy decisions and
rollback completeness. By default it writes nothing; --save FILE explicitly writes only
a digest-bound plan artifact, never registry state, audit records, or undo files.
> regx plan app.reg --output json
> regx plan desired.json --conflicts error --prune --redirect auto --save rollout.plan.json
> regx apply-plan rollout.plan.json -y
One denied destination blocks the whole plan, matching import's all-or-nothing policy boundary.
Redacted audit policy also redacts plan values to SHA-256 digests. Unreadable destinations, incomplete
rollback, skipped/refused redirects or policy denial exit with code 5.
With --view both, text and JSON contain separate 32-bit and 64-bit changes, failures,
policy denials and rollback paths.
Unredacted JSON before/after states also contain an exact
registry-value object with typed data or numeric type ID and raw bytes.
When policy requires redaction, only the existing SHA-256 evidence is emitted and
no exact payload is exposed.
A saved plan uses
schema v1 and binds its payload, every named source file,
each per-view desired mutation, and the relevant current registry state with SHA-256.
apply-plan rechecks all bindings and current administrative policy before writing, then
persists fresh per-view undo snapshots and applies with audited cross-view rollback. Any source or
current-state drift exits 5 without mutation. Stdin plans cannot be saved because the
source cannot be re-read. JSON output identifies a successfully
persisted artifact with savedPlan, its exact byte length,
and SHA-256; all three fields are null when no artifact was
requested or a blocked/incomplete plan was not written.
batch
Apply ordered registry operations as one compensation transaction. The versioned batch schema gives every operation a unique ID and embeds the same explicit JSON key/value representation used by conversion.
> regx batch rollout.batch.json --conflicts error --view both --backup rollout.undo.reg -y --output json
> regx batch rollout.batch.json --dry-run --output json
Before the first mutation, regx validates policy and captures every target in every selected view.
--conflicts error also rejects key/value collisions introduced inside an operation
by Smart Redirection before that shared snapshot is read.
The first failed operation stops the remainder and rolls all touched views back to that shared
pre-batch state. JSON reports each ID as applied, planned,
skipped, notAttempted, rolledBack, or
rollbackFailed, together with per-view apply details and rollback results.
Machine output identifies the separate
batch result schema v1.
Every per-view undo entry includes its path, exact byte length, and SHA-256.
Dry-run keeps the planned entries with null evidence; live and offline-hive
batches use the same contract.
audit and the audit trail
A tool that changes the registry and leaves no attributable record of what it changed cannot
be deployed in a managed environment. --audit-log appends one JSON object per
mutation — timestamp, actor SID, operation, before and after — to a file you
nominate.
# Every change recorded, then verified
regx import app.reg --audit-log "C:\logs\regx.jsonl"
regx audit "C:\logs\regx.jsonl"
regx audit "C:\logs\regx.jsonl" --rotate-to "C:\logs\regx-001.jsonl"
regx audit "C:\logs\regx-001.jsonl" --chain "C:\logs\regx.jsonl"
regx audit "C:\logs\regx.jsonl" --write-anchor "X:\anchors\regx.anchor"
regx audit "C:\logs\regx.jsonl" --verify-anchor "X:\anchors\regx.anchor"
regx audit "C:\logs\regx.jsonl" --write-anchor "X:\anchors\regx.anchor" --anchor-key "X:\keys\anchor.key"
records 7
sessions 3
sha256 78992f9622a90cd781a9cb442c6251105548f420e13dbc5abeef4c92a0a7469c
Chain intact: no record has been altered or removed.
Rotation refuses a broken log or an existing archive. The new segment begins with a hashed
segment.start record that binds the previous tail hash and archive SHA-256.
--chain verifies segments in chronological order and detects editing, omission, or
reordering. Run rotation while regx writers are quiescent; this CLI is not a logging service.
--write-anchor atomically stores the log SHA-256, tail hash, and record count in
a detached checkpoint. --verify-anchor reports internal-chain integrity and
checkpoint equality separately. Keep the anchor on another host, append-only storage, or a
signed change ticket; placing it beside the writable log does not create a separate trust boundary.
--anchor-key writes a v2 HMAC-SHA256 checkpoint and requires the same
32-byte-or-longer raw secret during verification. Signed anchors reject missing or wrong keys,
and keyed verification refuses unsigned v1 input to prevent downgrade. Protect the key with
ACLs and keep it on a separate trust boundary where possible.
JSON write results include the exact byte length and SHA-256 of the
persisted rotation archive or detached anchor. Dry-run uses
null evidence because no artifact exists yet.
Set REGX_AUDIT_LOG to enforce it machine-wide, so an individual invocation
cannot skip the trail by forgetting the flag. REGX_AUDIT_REDACT does the same
for redaction.
Why the records are chained
A log an attacker can quietly edit is not evidence. Every record carries the SHA-256 of the
previous record, so altering or removing a line breaks the chain from that point and
regx audit reports the line number and what it expected.
What this does and does not give you. It makes silent tampering detectable. It does not stop someone truncating the tail or rewriting the file wholesale — nothing held locally can, without a key the operator does not have. Ship the log somewhere append-only for that half of the problem. The chain is what turns "someone may have edited this" into "this line was edited".
Redaction
Registry values hold licence keys, tokens and connection strings, so a log that faithfully
records every byte written becomes a secret in its own right.
--audit-redact records the SHA-256 and byte length of each value instead of the
value — still enough to prove a specific value was written, or to compare two runs.
Redaction covers the recorded command line too. regx set … -d SECRET would
otherwise put the secret straight into the session header, which is exactly the hole an
early version of this had: the values were redacted and the command line was not.
A --dry-run is recorded with outcome simulated, so a rehearsal is
distinguishable from the real thing in the record. Failed operations are recorded too, with
the error — an attempt that was denied is as much a part of the trail as one that succeeded.
lnk: Known Folders and native Windows shortcuts
Every path-bearing CLI argument recognizes shell:Startup,
shell:Desktop, and shell:Programs. regx resolves these
names with SHGetKnownFolderPath, with the documented legacy
SHGetFolderPathW fallback. It does not expand a guessed environment
path or invoke an external shell.
# Create a current-user Startup shortcut through native COM
> regx lnk create --target "C:\Program Files\Acme\Acme.exe" --output "shell:Startup\Acme.lnk" --args=--background --workdir "C:\Program Files\Acme" --icon "C:\Program Files\Acme\Acme.exe,0" --style hidden -y
# Inspect without launching, rehearse, then remove
> regx lnk inspect "shell:Startup\Acme.lnk" --output json
> regx lnk delete "shell:Startup\Acme.lnk" --dry-run
> regx lnk delete "shell:Startup\Acme.lnk" -y
Creation uses CoCreateInstance(CLSID_ShellLink),
IShellLinkW, and IPersistFile. Target, arguments,
working directory, description, icon location/index, and show style are written to a
temporary link, read back through COM, verified, and atomically committed. The
hidden and minimized styles both request
SW_SHOWMINNOACTIVE; this controls initial window presentation and does not
conceal the Startup entry from Windows or regx.
All mutations support confirmation, -y, --dry-run, and
tamper-evident --audit-log records containing exact before/after shortcut
SHA-256 values. A shortcut target must be an existing absolute file, the destination
must end in .lnk, and inspect/delete refuse objects that cannot be parsed as
native links.
Shortcut manifests
lnk apply reads UTF-8 or BOM-marked UTF-16 from a file, -, or
pipe:NAME. It preflights every block, rejects duplicate destinations, asks
once, and rolls earlier writes back if a later action fails. Stream mutations require
-y because the stream is already consumed before confirmation.
[SHORTCUT]
Target=C:\Program Files\Acme\Acme.exe
Output=shell:Startup\Acme.lnk
WorkingDirectory=C:\Program Files\Acme
Arguments=--background
Description=Acme background client
Icon=C:\Program Files\Acme\Acme.exe,0
Style=hidden
[DELETE_SHORTCUT]
Path=shell:Desktop\Old Acme.lnk
> regx lnk apply startup.shortcuts --dry-run --output json
> Get-Content startup.shortcuts -Raw | regx lnk apply - -y
Automation can consume the shortcut result schema v1, the machine-readable capability inventory, or the compact AI-agent project summary.
Input formats
import, convert, sync and inspect all
accept any format below. Each reader produces the same internal model, so redirection,
coalescing, undo snapshots and apply behave identically whatever the input was.
The formats command prints the same inventory from the
executable, including detection notes, and supports
--output json for tooling.
> regx formats
> regx formats --output json
| Format | Typical file | Notes |
|---|---|---|
reg | .reg | UTF-16 Version 5.00 or ANSI REGEDIT4. |
pol | Registry.pol | Group Policy PReg binary. See below. |
admx | .admx + .adml | Policy template. Concrete values are read; administrator-supplied elements are fidelity losses. |
gpp | Registry.xml | Group Policy Preferences. Value R/U, all D, and key C/U are modeled; value C, key R, and targeting are fidelity losses. |
inf | .inf | [AddReg]/[DelReg] with [Strings] substitution. |
json | .json | Compact or explicit form. |
csv | .csv, .tsv | Header-driven columns, delimiter auto-detected. |
ini | .ini, .cfg | Section per key path. |
hive | NTUSER.DAT | Detected, then redirected to regx hive. |
Detection reads content first and the extension second. Override it with
--from <FORMAT> when a file is mislabelled.
Registry.pol
The binary a domain controller pushes down and the Group Policy engine replays. The cached
copies under %WINDIR%\System32\GroupPolicy and
…\GroupPolicyUsers are readable by ordinary users, so you can see exactly which
registry writes a policy performs — no elevation, no guessing.
| Directive | Handled as |
|---|---|
**del.Name | delete the value Name |
**delvals. | delete every value while preserving subkeys — reported as a fidelity loss; conversion and mutation fail closed because a key delete would be more destructive |
**DeleteValues | expands the ;-separated list into individual value deletes |
**DeleteKeys | expands into [-KEY] blocks for each named subkey |
**soft.Name | "write only if absent" — reported as a fidelity loss; conversion and mutation fail closed rather than overwriting an existing value |
**SecureKey, **ListElement | no registry effect; ignored and reported |
A .pol records no hive. The same bytes mean HKLM under
Machine\ and HKCU under User\, so the root has to come from
outside the file. regx infers it from the path and falls back to
--pol-root; the choice is always printed.
ADMX and ADML
An ADMX is a schema, not data: it declares which registry values a policy controls, not what an administrator chose. That distinction drives what is emitted.
Emitted, because it is concrete:
<enabledValue>/<disabledValue>on the policy's ownvalueName- the documented ADMX default when neither is declared — enabling writes
REG_DWORD 1, disabling writesREG_DWORD 0 <enabledList>/<disabledList>items, which carry literal values and may override the key
Reported, never emitted: <elements> — text,
decimal, boolean, enum, list,
multiText. Those hold whatever an administrator typed into the Group Policy
editor. Inventing a value for them would put fabricated data into the registry, so
regx inspect lists them as fidelity losses. Conversion and mutation fail
closed whenever such a loss exists. To see what was actually configured, read the
Registry.pol instead.
class picks the hive: Machine → HKLM, User → HKCU,
Both → emitted into both, which is what Windows does. An accompanying
.adml in a language folder is found automatically and resolves the
$(string.Id) display names. Choose the rendered state with
--admx-state enabled|disabled and narrow to one policy with
--admx-policy.
Group Policy Preferences
The other half of Group Policy. Where an ADMX declares a schema and a
Registry.pol carries the policy branch, GPP writes anywhere in the registry —
and its writes are not reverted when the GPO stops applying unless the item
says so. That is frequently why a setting keeps coming back.
Unconditional Replace and Update value writes are modeled, as
are all deletes and idempotent key Create/Update actions. The protocol's
default="1" attribute distinguishes a default value from a key-only item;
an empty name alone means the key. Value Create depends on absence, key Replace
deletes the complete subtree before recreation, bitfield/SubProp writes depend on the
current DWORD, item-level targeting depends on the client environment, and
removePolicy="1" requires a future undo when the GPO leaves scope. Those cases
are fidelity losses and block conversion/mutation rather
than being flattened. <Collection>
groups are traversed, REG_MULTI_SZ entries are read from the
<Values> children rather than the value attribute, and items
under an outer RegistrySettings disabled="1" are skipped as a disabled
preference type. Non-schema item-level disabled attributes are rejected.
The reader traverses only the protocol's
RegistrySettings/Collection/Registry grammar;
registry-looking descendants under unrelated XML wrappers are not accepted.
Content detection uses that parsed root too, so renamed valid fragments remain
detectable without classifying arbitrary XML by a nested tag substring.
Both XML readers refuse a DOCTYPE rather than skipping it.
A DOCTYPE is where external-entity and billion-laughs attacks live, and neither format
needs one — an XML parser that resolves external entities in a tool reading files from a
policy share is an XXE vulnerability waiting to happen. Nesting depth is bounded too.
INF
Reads every [AddReg] and [DelReg] section named by an
AddReg=/DelReg= directive; restrict it with
--inf-section. %Token% references resolve against
[Strings], and %% stays a literal percent. Microsoft requires
every token to be defined, so an unknown or unterminated token is reported as a fidelity
loss instead of being turned into registry data Windows would not have installed.
For international INF files, --inf-language 0409 selects a four-digit
Windows LANGID and follows SetupAPI's exact, neutral, same-language-family, then
undecorated [Strings] fallback order. Without the option, the undecorated
section is selected deliberately rather than depending on the workstation locale.
Physical lines ending in an unquoted \ are joined before parsing.
Quoted token definitions preserve edge whitespace and semicolons, collapse
"" to one quote, and reject duplicate or unterminated definitions instead
of silently choosing one.
HKR is a fidelity loss because it is relative to a driver install context
that only exists inside SetupAPI. NOCLOBBER, APPEND,
OVERWRITEONLY, and per-line 32/64-bit view flags likewise require state or
routing absent from the common model, while a referenced [Section.security]
changes ACLs. They block conversion and mutation. Exact key-only and delete flags remain
usable, and custom binary registry types encoded as 0xTYPE0001 retain their
numeric type ID and bytes.
JSON
The compact form is what you write by hand. Types map on sight.
{
"HKCU\\Software\\Acme": {
"Server": "acme.test", // REG_SZ
"Port": 8080, // REG_DWORD
"Enabled": true, // REG_DWORD 1
"Recent": ["a.txt", "b.txt"], // REG_MULTI_SZ
"Blob": { "type": "REG_BINARY", "data": "de ad be ef" },
"Legacy": null // delete the value
}
}
An integer too wide for a DWORD widens to REG_QWORD rather than truncating.
Floating-point numbers are refused: the registry has no such type, and silently rounding
would be worse than an error. Registry paths need doubled backslashes —
"HKCU\\Software", not "HKCU\Software" — because a lone backslash
is not a valid JSON escape.
The explicit form adds key deletion and named types:
{ "keys": [
{ "path": "HKCU\\Software\\Gone", "delete": true },
{ "path": "HKCU\\Software\\Acme",
"values": [ { "name": "Port", "type": "REG_DWORD", "data": 8080 } ] }
] }
CSV
Columns are matched by header name in any order, case-insensitively, so a sheet exported
from Excel works unedited. Quoting follows RFC 4180. An empty type and data deletes
the value; DELETE_KEY in the data column with no value name deletes the key.
key,name,type,data
HKCU\Software\Acme,Server,REG_SZ,acme.test
HKCU\Software\Acme,Port,REG_DWORD,8080
HKCU\Software\Acme,Legacy,, # delete the value
HKCU\Software\Old,,,DELETE_KEY # delete the key
INI
A section header is a full registry path and [-Path] deletes the key, both
borrowed from .reg. The optional :type suffix is the only addition;
without it a value is REG_SZ, which is what an ordinary INI means anyway.
REG_MULTI_SZ entries are separated with |, which reads better here
than reg.exe's \0.
[HKEY_CURRENT_USER\Software\Acme]
Server = acme.test
Port:dword = 8080
Path:expand_sz = %USERPROFILE%\acme
Recent:multi_sz = a.txt|b.txt
Blob:binary = 01 02 ff
Legacy = ; empty value deletes it
@ = default value
[-HKEY_CURRENT_USER\Software\Old]
Redirection flags
Shared by import, convert, sync and set.
| Flag | Values | Meaning |
|---|---|---|
--redirect |
off, auto, classes-only, force |
Default auto. classes-only maps just Software\Classes, the one mapping reliable by design. force implies the lowest confidence floor. |
--min-confidence |
high, medium, low |
Default medium. Mappings weaker than this are skipped and reported. |
--on-refuse |
skip, fail |
What to do with keys that have no per-user equivalent at all. Default skip. |
SOFTWARE\WOW6432Node\X is normalised to SOFTWARE\X before classification, so
32-bit and 64-bit exports of the same application collapse onto one destination. Because that collapse
produces duplicate key blocks, every redirect run is followed by a case-insensitive coalesce pass.
Active Setup, User Shell Folders/Shell Folders and Winlogon are recognised
explicitly and refused under HKLM. Their machine and user
branches have different semantics: Active Setup's HKCU entry is a completion
marker, known folders should target the existing user profile (preferably
through the Windows Known Folder API), and Winlogon's
Shell/Userinit values can make sign-in unusable.
Value types
set follows reg.exe conventions, so muscle memory transfers.
| Type | -d format | Example |
|---|---|---|
REG_SZ | Literal text | -d "C:\Tools" |
REG_EXPAND_SZ | Text with environment variables | -d "%USERPROFILE%\bin" |
REG_MULTI_SZ | Entries separated by a literal \0 | -d "a.txt\0b.txt" |
REG_DWORD | Decimal or 0x hex | -d 25 or -d 0x19 |
REG_QWORD | Decimal or 0x hex | -d 0xdeadbeef |
REG_BINARY | Hex digits, separators optional | -d "01 02 03" |
Getting past AppLocker, SRP and WDAC
UAC is not the real obstacle in a locked-down enterprise; application control is. An unsigned
.exe under %TEMP%, Downloads or %APPDATA% is precisely
the shape the default rule sets deny. regx --self-check reads the relevant policy keys —
all readable by a standard user — and reports what applies.
-
Sign the binary
A publisher rule follows the file anywhere; a path rule does not. An EV certificate additionally carries immediate SmartScreen reputation, which a standard OV certificate has to accumulate. In a managed environment a certificate from the organisation's internal CA is usually faster to obtain and already trusted domain-wide.
-
Run from a path the policy already allows
Typically
%ProgramFiles%or an IT-managed share, rather thanDownloads. -
Clear the Mark-of-the-Web
Unblock-Fileremoves theZone.Identifierstream that triggers the SmartScreen interstitial.
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /a regx.exe
Always timestamp with /tr — without it, signatures stop validating when the certificate expires.
WDAC is different. It ignores file location entirely. If a user-mode code-integrity policy is deployed, only a signature or an explicit hash rule will let the binary run — steps 2 and 3 do not help.
.reg format notes
Behaviours that surprise people writing .reg files by hand, all of which regx matches to regedit:
- Two escapes only.
\\and\". There is no\nor\t— writing one produces a literal backslash followed by that character. - Two encodings.
Windows Registry Editor Version 5.00is UTF-16LE with a BOM;REGEDIT4is ANSI in the machine's codepage. A REGEDIT4 file written on one codepage decodes differently on another. - Continuations are hex-only. A trailing
\continues ahexpayload. A string value ending in"C:\\"is not a continuation. - Strings need terminators.
hex(1),hex(2)andhex(7)payloads must be NUL-terminated —REG_MULTI_SZdoubly so. Without them the consuming application reads past the value. - Multiline or embedded-NUL
REG_SZremains lossless. Because quoted.regstrings cannot escape those controls, the writer automatically emits NUL-terminated UTF-16LEhex(1)instead. - Names are checked before Win32 or the writer sees them. Embedded NUL is rejected rather than silently truncating a name; line-breaking control characters are refused because
.reghas no name escape for them. Key components and value names are limited to 255 and 16,383 UTF-16 code units respectively. - Comments are line-initial. A
;starts a comment only at the beginning of a line, and its behaviour inside a hex continuation is inconsistent in regedit itself.
regx preserves values it cannot model losslessly as raw hex(N) bytes, and only
writes a REG_SZ as a quoted string when it is clean UTF-16 — even length, single trailing NUL,
no embedded NUL, no control characters. A raw newline inside a quoted string would corrupt the next line
of the file.