Repository navigation
WOW64 Registry Handling
Relevant source files
The following files were used as context for generating this wiki page:
This document describes the WOW64 (Windows 32-bit on Windows 64-bit) registry handling implementation in OfficeScrubC2R. The system ensures complete cleanup of Office installations by accessing both the native 64-bit registry view and the redirected 32-bit (WOW64) registry view on 64-bit Windows systems. This is critical because Office can be installed as either a 32-bit or 64-bit application, leaving artifacts in different registry locations.
For information about the overall architecture, see Architecture. For details about registry cleanup operations in the three-stage pipeline, see Three-Stage Execution Pipeline. For performance optimizations, see Performance Architecture.
On 64-bit Windows systems, the registry is divided into two views to support both 64-bit and 32-bit applications. Windows automatically redirects certain registry paths when accessed by 32-bit applications:
| Registry Path | 64-bit View | 32-bit (WOW64) View |
|---|---|---|
HKLM\Software\Microsoft\Office |
Native 64-bit location | HKLM\Software\Wow6432Node\Microsoft\Office |
HKLM\Software\Classes\TypeLib |
Native 64-bit location | HKLM\Software\Classes\Wow6432Node\TypeLib |
HKCU\Software\Microsoft\Office |
Native 64-bit location | HKCU\Software\Wow6432Node\Microsoft\Office |
Office Click-to-Run installations can exist in either view depending on whether 32-bit or 64-bit Office was installed. To ensure complete cleanup, OfficeScrubC2R must check and remove artifacts from both registry views.
Sources: OfficeScrubC2R-Native.cs:334-616, docs/memory-bank/guidelines.md:57-61
The RegistryHelper class implements the dual-path registry access pattern through a simple but effective architecture:
graph TB
Constructor["RegistryHelper(bool is64Bit)"] --> Init["Initialize _is64Bit field"]
subgraph "Core Operations"
KeyExists["KeyExists()"]
DeleteKey["DeleteKey()"]
DeleteValue["DeleteValue()"]
EnumerateKeys["EnumerateKeys()"]
EnumerateValues["EnumerateValues()"]
GetValue["GetValue()"]
end
subgraph "Internal Methods"
GetHiveKey["GetHiveKey(hive)"]
GetWow64Key["GetWow64Key(subKey)"]
KeyExistsInternal["KeyExistsInternal()"]
DeleteKeyInternal["DeleteKeyInternal()"]
OpenKey["OpenKey()"]
end
subgraph "Path Resolution"
NativePath["Native Path<br/>HKLM\\Software\\X"]
WOW64Path["WOW64 Path<br/>HKLM\\Software\\Wow6432Node\\X"]
end
KeyExists --> GetHiveKey
KeyExists --> KeyExistsInternal
KeyExists --> Is64Bit{_is64Bit?}
Is64Bit -->|Yes| GetWow64Key
GetWow64Key --> WOW64Path
KeyExistsInternal --> NativePath
DeleteKey --> GetHiveKey
DeleteKey --> Is64Bit
DeleteKey --> DeleteKeyInternal
DeleteValue --> GetHiveKey
DeleteValue --> Is64Bit
DeleteValue --> OpenKey
EnumerateKeys --> GetHiveKey
EnumerateKeys --> Is64Bit
EnumerateKeys --> HashSetDedup["HashSet<string><br/>StringComparer.OrdinalIgnoreCase"]
style Constructor fill:#f9f9f9
style Is64Bit fill:#ffe6e6
style HashSetDedup fill:#e6f3ff
Diagram: RegistryHelper Class Architecture and Method Flow
The _is64Bit field is the central decision point for all registry operations. When true, every operation performs dual-path access.
Sources: OfficeScrubC2R-Native.cs:334-342, OfficeScrubC2R-Native.cs:566-581
The GetWow64Key() method transforms native registry paths into their WOW64 equivalents by injecting Wow6432Node at the appropriate location:
flowchart TD
Start["GetWow64Key(string subKey)"] --> CheckClasses{"subKey starts with<br/>'Software\\Classes\\'?"}
CheckClasses -->|Yes| ClassesNode["Insert after 'Software\\Classes\\'<br/>Return: 'Software\\Classes\\Wow6432Node\\' + remainder"]
CheckClasses -->|No| FindBackslash["Find first backslash position"]
FindBackslash --> HasBackslash{"Position > 0?"}
HasBackslash -->|Yes| InsertMiddle["Insert after first segment<br/>Return: segment + '\\Wow6432Node\\' + remainder"]
HasBackslash -->|No| PrependNode["Return: 'Wow6432Node\\' + subKey"]
ClassesNode --> End["Return WOW64 Path"]
InsertMiddle --> End
PrependNode --> End
style Start fill:#f9f9f9
style End fill:#e6ffe6
style CheckClasses fill:#fff4e6
Diagram: GetWow64Key Path Transformation Logic
| Input Path | Output WOW64 Path |
|---|---|
Software\Microsoft\Office\16.0 |
Software\Wow6432Node\Microsoft\Office\16.0 |
Software\Classes\TypeLib\{GUID} |
Software\Classes\Wow6432Node\TypeLib\{GUID} |
Installer\Products |
Wow6432Node\Installer\Products |
The algorithm handles three cases:
-
Software\Classes paths: Insert
Wow6432NodeafterSoftware\Classes\ -
Paths with backslash: Insert
Wow6432Nodeafter the first segment -
Simple paths: Prepend
Wow6432Node\
Sources: OfficeScrubC2R-Native.cs:583-593, docs/OfficeScrubC2R-Native-Optimized-Real.cs:554-564
Each RegistryHelper public method implements the dual-path pattern. The following sections detail the implementation for each operation type.
sequenceDiagram
participant Caller
participant KeyExists
participant GetHiveKey
participant KeyExistsInternal
participant NativeReg as "Native Registry View"
participant GetWow64Key
participant WOW64Reg as "WOW64 Registry View"
Caller->>KeyExists: KeyExists(hive, subKey)
KeyExists->>GetHiveKey: GetHiveKey(hive)
GetHiveKey-->>KeyExists: hiveKey
KeyExists->>KeyExistsInternal: Check native view
KeyExistsInternal->>NativeReg: OpenSubKey(subKey)
NativeReg-->>KeyExistsInternal: key or null
KeyExistsInternal-->>KeyExists: exists1
alt _is64Bit == true
KeyExists->>GetWow64Key: GetWow64Key(subKey)
GetWow64Key-->>KeyExists: wow64SubKey
KeyExists->>KeyExistsInternal: Check WOW64 view
KeyExistsInternal->>WOW64Reg: OpenSubKey(wow64SubKey)
WOW64Reg-->>KeyExistsInternal: key or null
KeyExistsInternal-->>KeyExists: exists2
end
KeyExists->>KeyExists: return exists1 OR exists2
KeyExists-->>Caller: bool result
Diagram: KeyExists Dual-Path Execution Flow
The KeyExists() method returns true if the key exists in either the native view or the WOW64 view:
return KeyExistsInternal(GetHiveKey(hive), subKey, false) ||
(_is64Bit && KeyExistsInternal(GetHiveKey(hive), GetWow64Key(subKey), false));
This OR logic ensures that a key is detected regardless of which view contains it.
Sources: OfficeScrubC2R-Native.cs:343-347, docs/OfficeScrubC2R-Native-Optimized-Real.cs:347-352
The DeleteKey() method performs sequential deletion attempts on both views:
flowchart TD
Start["DeleteKey(hive, subKey, recursive)"] --> Init["result = false"]
Init --> Check1["KeyExists(hive, subKey)?"]
Check1 -->|Yes| Delete1["DeleteKeyInternal(hiveKey, subKey, recursive)<br/>result |= deleteResult"]
Check1 -->|No| Check2
Delete1 --> Check2
Check2{"_is64Bit?<br/>KeyExists(hive, wow64Key)?"}
Check2 -->|Yes| GetWOW["wow64Key = GetWow64Key(subKey)"]
GetWOW --> Delete2["DeleteKeyInternal(hiveKey, wow64Key, recursive)<br/>result |= deleteResult"]
Check2 -->|No| Return
Delete2 --> Return["return result"]
style Start fill:#f9f9f9
style Return fill:#e6ffe6
style Check2 fill:#fff4e6
Diagram: DeleteKey Dual-Path Deletion Logic
Key implementation details:
- Uses OR-assignment (
result || result) to track if any deletion succeeded - Checks existence before deletion to avoid unnecessary operations
- Performs WOW64 deletion independently of native deletion success
- Returns
trueif deletion succeeded in either view
Sources: OfficeScrubC2R-Native.cs:349-364, docs/OfficeScrubC2R-Native-Optimized-Real.cs:354-370
flowchart TD
Start["DeleteValue(hive, subKey, valueName)"] --> Init["result = false"]
Init --> Native["Try native path:<br/>OpenKey(hiveKey, subKey, writable=true)"]
Native --> CheckNative{"key != null &&<br/>value exists?"}
CheckNative -->|Yes| DelNative["key.DeleteValue(valueName)<br/>result = true"]
CheckNative -->|No| CheckFlag
DelNative --> CheckFlag
CheckFlag{"_is64Bit?"}
CheckFlag -->|Yes| WOW64["Try WOW64 path:<br/>OpenKey(hiveKey, GetWow64Key(subKey), writable=true)"]
WOW64 --> CheckWOW{"key != null &&<br/>value exists?"}
CheckWOW -->|Yes| DelWOW["key.DeleteValue(valueName)<br/>result = true"]
CheckWOW -->|No| Return
CheckFlag -->|No| Return
DelWOW --> Return["return result"]
style Start fill:#f9f9f9
style Return fill:#e6ffe6
style CheckFlag fill:#fff4e6
Diagram: DeleteValue Dual-Path Value Deletion
The implementation uses separate try-catch blocks for each view to ensure that a failure in one view doesn't prevent deletion from the other view.
Sources: OfficeScrubC2R-Native.cs:366-400, docs/OfficeScrubC2R-Native-Optimized-Real.cs:372-406
The EnumerateKeys() method collects subkey names from both views and deduplicates using a HashSet:
flowchart TB
Start["EnumerateKeys(hive, subKey)"] --> CreateSet["Create HashSet<string><br/>StringComparer.OrdinalIgnoreCase"]
CreateSet --> GetHive["hiveKey = GetHiveKey(hive)"]
GetHive --> Native["AddKeysFromPath(hiveKey, subKey, keys)"]
Native --> NativeOpen["OpenSubKey(subKey, writable=false)"]
NativeOpen --> NativeEnum["foreach name in key.GetSubKeyNames()<br/>keys.Add(name)"]
NativeEnum --> CheckFlag{"_is64Bit?"}
CheckFlag -->|Yes| WOW64["AddKeysFromPath(hiveKey, GetWow64Key(subKey), keys)"]
CheckFlag -->|No| Convert
WOW64 --> WOWOpen["OpenSubKey(GetWow64Key(subKey), writable=false)"]
WOWOpen --> WOWEnum["foreach name in key.GetSubKeyNames()<br/>keys.Add(name)"]
WOWEnum --> Convert["return keys.ToArray()"]
style Start fill:#f9f9f9
style CreateSet fill:#e6f3ff
style Convert fill:#e6ffe6
Diagram: EnumerateKeys Deduplication Strategy
Deduplication mechanism:
- Initialize
HashSet<string>withStringComparer.OrdinalIgnoreCase - Add all subkeys from native view
- Add all subkeys from WOW64 view (if 64-bit system)
- HashSet automatically deduplicates case-insensitively
- Convert to array and return
This ensures that if a key name appears in both views, it only appears once in the result.
Sources: OfficeScrubC2R-Native.cs:402-436, docs/OfficeScrubC2R-Native-Optimized-Real.cs:408-432
The EnumerateValues() method uses the identical deduplication pattern for registry value names:
HashSet<string> values = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
The same case-insensitive deduplication ensures value names are collected from both views without duplicates.
Sources: OfficeScrubC2R-Native.cs:438-472, docs/OfficeScrubC2R-Native-Optimized-Real.cs:434-458
The GetValue() method uses a fallback pattern instead of OR logic:
flowchart TD
Start["GetValue(hive, subKey, valueName, defaultValue)"] --> GetHive["hiveKey = GetHiveKey(hive)"]
GetHive --> Native["result = GetValueFromPath(hiveKey, subKey, valueName, defaultValue)"]
Native --> CheckResult{"result == null?"}
CheckResult -->|No| Return
CheckResult -->|Yes| CheckFlag{"_is64Bit?"}
CheckFlag -->|No| Return
CheckFlag -->|Yes| WOW64["result = GetValueFromPath(hiveKey,<br/>GetWow64Key(subKey), valueName, defaultValue)"]
WOW64 --> Return["return result ?? defaultValue"]
style Start fill:#f9f9f9
style Return fill:#e6ffe6
style CheckFlag fill:#fff4e6
style CheckResult fill:#fff4e6
Diagram: GetValue Fallback Pattern
Behavior:
- Try to read from native view
- If result is
nulland system is 64-bit, try WOW64 view - Return first non-null result or
defaultValue
This prioritizes the native view but falls back to WOW64 if needed.
Sources: OfficeScrubC2R-Native.cs:474-502, docs/OfficeScrubC2R-Native-Optimized-Real.cs:460-482
The SetValue() method is an exception to the dual-path pattern—it only writes to the native view:
public bool SetValue(RegistryHiveType hive, string subKey, string valueName, object value, RegistryValueKind kind)
{
// Only writes to native view, no WOW64 logic
}
Rationale: Setting values is not part of the cleanup process. This method exists for the PendingFileRenameOperations feature, which should only write to the native view.
Sources: OfficeScrubC2R-Native.cs:504-521, docs/OfficeScrubC2R-Native-Optimized-Real.cs:484-501
The WOW64 registry handling is transparently integrated into all cleanup operations through the RegistryHelper class:
graph TB
Orchestrator["OfficeScrubOrchestrator"] -->|contains| RegHelper["RegistryHelper(_is64Bit)"]
subgraph "Cleanup Helpers"
WindowsInstaller["WindowsInstallerHelper"]
TypeLib["TypeLibHelper"]
end
Orchestrator --> WindowsInstaller
Orchestrator --> TypeLib
WindowsInstaller -->|uses| RegHelper
TypeLib -->|uses| RegHelper
subgraph "Registry Operations"
CleanupUpgradeCodes["CleanupUpgradeCodes()"]
CleanupProducts["CleanupProducts()"]
CleanupComponents["CleanupComponents()"]
CleanupKnownTypeLibs["CleanupKnownTypeLibs()"]
end
WindowsInstaller --> CleanupUpgradeCodes
WindowsInstaller --> CleanupProducts
WindowsInstaller --> CleanupComponents
TypeLib --> CleanupKnownTypeLibs
CleanupUpgradeCodes -->|EnumerateKeys| RegHelper
CleanupUpgradeCodes -->|DeleteKey| RegHelper
CleanupProducts -->|EnumerateKeys| RegHelper
CleanupProducts -->|DeleteKey| RegHelper
CleanupComponents -->|EnumerateValues| RegHelper
CleanupComponents -->|DeleteValue| RegHelper
CleanupKnownTypeLibs -->|KeyExists| RegHelper
CleanupKnownTypeLibs -->|DeleteKey| RegHelper
style RegHelper fill:#e6f3ff
style Orchestrator fill:#f9f9f9
Diagram: RegistryHelper Integration with Cleanup Operations
Higher-level helpers receive the RegistryHelper instance through constructor injection and use it for all registry operations. The WOW64 handling is completely transparent—cleanup code doesn't need to be aware of dual-path logic.
Example from WindowsInstallerHelper:
public void CleanupUpgradeCodes(Func<string, bool> shouldDelete)
{
var keys = _regHelper.EnumerateKeys(RegistryHiveType.LocalMachine, path);
// EnumerateKeys automatically returns keys from both views
foreach (var key in keys)
{
if (shouldDelete(guid))
{
_regHelper.DeleteKey(RegistryHiveType.LocalMachine, path + "\\" + key);
// DeleteKey automatically removes from both views
}
}
}Sources: OfficeScrubC2R-Native.cs:980-1011, OfficeScrubC2R-Native.cs:1134-1209, OfficeScrubC2R-Native.cs:1349-1383
The deduplication strategy uses StringComparer.OrdinalIgnoreCase for optimal performance:
var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);Benefits:
- O(1) lookup and insertion
- Case-insensitive comparison without
.ToLower()allocations - Automatic deduplication of keys that differ only in case
Sources: OfficeScrubC2R-Native.cs:404, OfficeScrubC2R-Native.cs:440
The DeleteKey() method checks existence before attempting deletion:
if (KeyExists(hive, subKey))
{
result = DeleteKeyInternal(GetHiveKey(hive), subKey, recursive);
}This avoids opening registry keys unnecessarily when the key doesn't exist in either view.
Sources: OfficeScrubC2R-Native.cs:353-356
Each registry view operation uses separate try-catch blocks to ensure failures in one view don't block operations on the other view:
try { /* Native view operation */ } catch { }
if (_is64Bit) {
try { /* WOW64 view operation */ } catch { }
}This provides resilience against permission issues or corrupted registry keys in one view.
Sources: OfficeScrubC2R-Native.cs:370-400, OfficeScrubC2R-Native.cs:406-433
| Class/Method | File Location | Purpose |
|---|---|---|
RegistryHelper |
OfficeScrubC2R-Native.cs:334-616 | Main registry operations class with WOW64 support |
RegistryHelper._is64Bit |
OfficeScrubC2R-Native.cs:336 | System architecture flag for dual-path decision |
GetWow64Key() |
OfficeScrubC2R-Native.cs:583-593 | Path transformation for WOW64 view |
KeyExists() |
OfficeScrubC2R-Native.cs:343-347 | Checks both native and WOW64 views |
DeleteKey() |
OfficeScrubC2R-Native.cs:349-364 | Deletes from both views |
DeleteValue() |
OfficeScrubC2R-Native.cs:366-400 | Deletes values from both views |
EnumerateKeys() |
OfficeScrubC2R-Native.cs:402-436 | Enumerates keys with deduplication |
EnumerateValues() |
OfficeScrubC2R-Native.cs:438-472 | Enumerates values with deduplication |
GetValue() |
OfficeScrubC2R-Native.cs:474-502 | Reads with native-to-WOW64 fallback |
Sources: OfficeScrubC2R-Native.cs:334-616, docs/OfficeScrubC2R-Native-Optimized-Real.cs:338-587