Windows registry values are typed. What is the difference between REG_SZ and REG_EXPAND_SZ, and what must code reading the latter do differently?
answer
- both hold text, one holds placeholders
- %SystemRoot% is not a path yet
- who calls ExpandEnvironmentStrings?
- raw query versus friendly wrapper
- service ImagePath uses the expandable one
basics
~20 sREG_SZ is a plain string stored literally. REG_EXPAND_SZ is a string that still contains unexpanded environment-variable references such as %SystemRoot%, so the reader must expand it before use — otherwise it gets a path that does not exist.
solid answer
~40 sBoth are string types, but they carry a different contract with the reader. `REG_SZ` is taken literally: what was written is what you use. `REG_EXPAND_SZ` deliberately stores placeholders like `%SystemRoot%\system32\svchost.exe`, and the consumer is expected to run it through `ExpandEnvironmentStrings` before treating it as a path. The trap is that the low-level `RegQueryValueEx` returns the raw, unexpanded string, while the higher-level `RegGetValue` expands it for you unless you pass `RRF_NOEXPAND` — so two pieces of code reading the same value can legitimately see different text. Service `ImagePath` values are commonly `REG_EXPAND_SZ`, which is how a service definition stays valid on a machine whose Windows directory is not on C:. The other everyday types are `REG_DWORD` and `REG_QWORD` for numbers, `REG_MULTI_SZ` for a null-separated list of strings, and `REG_BINARY` for opaque blobs.
code
powershell · 10 linesNew-Item -Path 'HKCU:\Software\Contoso' -Force | Out-Null
New-ItemProperty -Path 'HKCU:\Software\Contoso' -Name 'DataDir' `
-PropertyType ExpandString -Value '%ProgramData%\Contoso' -Force | Out-Null
# Expanded by default
(Get-ItemProperty 'HKCU:\Software\Contoso').DataDir
# Literal, unexpanded text
$key = Get-Item 'HKCU:\Software\Contoso'
$key.GetValue('DataDir', $null, 'DoNotExpandEnvironmentNames')go deeper
Know that registry values are typed, and that a value containing %SystemRoot% is a REG_EXPAND_SZ whose placeholders must be expanded before you use it as a path.
Explain that RegQueryValueEx returns the raw string while RegGetValue expands unless RRF_NOEXPAND is passed, and name the everyday types: REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_DWORD, REG_BINARY.
Show the deployment consequence: a script that reads with an expanding API and writes back bakes machine-specific paths into a service definition, which breaks on the next imaged host. Preserve type and literal text on round-trips.
Frame it as configuration hygiene at fleet scale — machine-portable values, typed writes validated by tooling, and a policy on which settings are allowed to be hand-edited at all versus delivered through managed configuration.
## Values are typed, and the type is a contract A registry value is a triple: a name, a type tag, and a blob of data. The type tag is not decoration — it tells every reader how to interpret the bytes, and writing the wrong type is a real bug even when the bytes look right. A component expecting `REG_DWORD` and finding the string `"1"` in a `REG_SZ` typically reads it as absent or invalid, which is the classic cause of "I set the value and nothing changed". ## The string types **REG_SZ** is a null-terminated Unicode string, used as-is. **REG_EXPAND_SZ** is the same storage format with a different promise: the string is allowed to contain environment-variable references in `%NAME%` form, and it is the *reader's* job to expand them. The canonical example is a service image path: ``` ImagePath REG_EXPAND_SZ %SystemRoot%\system32\svchost.exe -k netsvcs ``` Stored this way, the entry is correct whether Windows is installed on `C:\Windows` or somewhere else, and it survives machine imaging. Stored as a literal `REG_SZ` with a hardcoded drive letter, it is brittle. **REG_MULTI_SZ** is a sequence of null-terminated strings terminated by an extra null — a list. Editing one by hand in a tool that does not understand the double-null convention is an easy way to truncate the list. ## Who expands, and who does not This is the part interviewers actually probe, because the behaviour is inconsistent by design: - `RegQueryValueEx` hands back the raw bytes. If the value is `REG_EXPAND_SZ`, you receive `%SystemRoot%\...` verbatim and must call `ExpandEnvironmentStrings` yourself. - `RegGetValue` expands `REG_EXPAND_SZ` for you by default; pass `RRF_NOEXPAND` when you want the literal text (for example, because you are about to rewrite the value and do not want to bake in this machine's paths). - In .NET and PowerShell, `RegistryKey.GetValue` expands by default; the `RegistryValueOptions.DoNotExpandEnvironmentNames` option suppresses it. The practical failure mode: an administrative script reads a service's `ImagePath` with an expanding API, edits it, and writes it back — now the value is a hardcoded absolute path, and the change quietly breaks on the next machine that image is cloned to. ## The numeric and binary types **REG_DWORD** is a 32-bit integer, by far the most common way Windows expresses a flag or a timeout in milliseconds. **REG_QWORD** is its 64-bit sibling. **REG_BINARY** is an opaque byte blob whose layout only its owning component knows; treating one as human-editable is usually a mistake. There is also **REG_NONE** (no defined type) and **REG_LINK** (a symbolic link between keys, used internally — `CurrentControlSet` is one). ## Creating values with the right type `reg.exe` takes the type explicitly with `/t`, and PowerShell's `New-ItemProperty` takes `-PropertyType`, where the names differ from the Win32 constants: `String` maps to `REG_SZ`, `ExpandString` to `REG_EXPAND_SZ`, `MultiString` to `REG_MULTI_SZ`, `DWord` to `REG_DWORD`, `QWord` to `REG_QWORD`, `Binary` to `REG_BINARY`. ``` reg add "HKLM\SOFTWARE\Contoso" /v DataDir /t REG_EXPAND_SZ /d "%ProgramData%\Contoso" /f ``` ## Why it is worth knowing Most registry-related deployment bugs are not exotic. They are a `REG_SZ` where a `REG_DWORD` was expected, a `REG_EXPAND_SZ` written literally so `%ProgramData%` never resolves, or a `REG_MULTI_SZ` list that lost its terminator. Getting the type right is the whole of the discipline.
- What goes wrong if you write a flag as REG_SZ when the component expects REG_DWORD?The component's read fails the type check and it falls back to its built-in default, so the setting silently has no effect. Nothing logs an error in most cases, which is why the symptom presents as "I set the key and nothing happened". Always confirm the documented type before writing, and verify afterwards that the value's type tag — not just its text — matches.
- Why is REG_MULTI_SZ easy to corrupt by hand?It is a run of null-terminated strings closed by one extra null. Editors that treat the data as a single string can drop the final terminator or turn embedded nulls into visible separators, and the reader then sees a truncated list or one nonsense entry. Use an API or tool that understands the type — `reg add /t REG_MULTI_SZ /s` with a separator, or PowerShell's `MultiString` property type with a real array.
saying these in an interview costs you the question
- Says REG_EXPAND_SZ is expanded automatically for every caller
- Treats REG_SZ and REG_DWORD as interchangeable if the digits match
- Thinks the type tag is only a hint for regedit's display
- Rewrites an ImagePath as a hardcoded absolute path