Skip to content

WOW64 Registry Handling

Calvindd2f edited this page Jan 14, 2026 · 1 revision

Relevant source files

The following files were used as context for generating this wiki page:

Purpose and Scope

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.

WOW64 Registry Redirection Background

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

RegistryHelper Architecture

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
Loading

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

Path Transformation with GetWow64Key

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
Loading

Diagram: GetWow64Key Path Transformation Logic

Transformation Examples

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:

  1. Software\Classes paths: Insert Wow6432Node after Software\Classes\
  2. Paths with backslash: Insert Wow6432Node after the first segment
  3. Simple paths: Prepend Wow6432Node\

Sources: OfficeScrubC2R-Native.cs:583-593, docs/OfficeScrubC2R-Native-Optimized-Real.cs:554-564

Dual-Path Registry Operations

Each RegistryHelper public method implements the dual-path pattern. The following sections detail the implementation for each operation type.

KeyExists: Checking Both Views

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
Loading

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

DeleteKey: Removing from Both Views

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
Loading

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 true if deletion succeeded in either view

Sources: OfficeScrubC2R-Native.cs:349-364, docs/OfficeScrubC2R-Native-Optimized-Real.cs:354-370

DeleteValue: Dual-Path Value Removal

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
Loading

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

EnumerateKeys: Deduplicating Results

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
Loading

Diagram: EnumerateKeys Deduplication Strategy

Deduplication mechanism:

  1. Initialize HashSet<string> with StringComparer.OrdinalIgnoreCase
  2. Add all subkeys from native view
  3. Add all subkeys from WOW64 view (if 64-bit system)
  4. HashSet automatically deduplicates case-insensitively
  5. 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

EnumerateValues: Similar Deduplication Pattern

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

GetValue: Fallback Pattern

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
Loading

Diagram: GetValue Fallback Pattern

Behavior:

  1. Try to read from native view
  2. If result is null and system is 64-bit, try WOW64 view
  3. 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

SetValue: Native View Only

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

Integration with Cleanup Operations

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
Loading

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

Performance Considerations

Case-Insensitive HashSet

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

Existence Checks Before Deletion

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

Independent Error Handling

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

Code Reference Summary

Key Classes and Methods

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

Clone this wiki locally