Skip to content

WindowsInstallerHelper

Calvindd2f edited this page Jan 14, 2026 · 1 revision

WindowsInstallerHelper

Relevant source files

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

Purpose and Scope

The WindowsInstallerHelper class provides specialized functionality for cleaning up Windows Installer (MSI) metadata from the registry. It handles removal of UpgradeCodes, Products, Components, and PublishedComponents registry entries associated with Office installations, with sophisticated GUID encoding/decoding capabilities to handle Microsoft's compressed GUID format used in installer metadata.

This page documents the internal mechanics of MSI metadata cleanup. For overall registry operations, see RegistryHelper. For the orchestration layer that coordinates these operations, see Native Library Overview.


Class Overview

The WindowsInstallerHelper class is a specialized helper that focuses exclusively on Windows Installer metadata cleanup. It operates on four distinct metadata categories in the Windows registry, each requiring different handling strategies due to GUID encoding variations.

classDiagram
    class WindowsInstallerHelper {
        -RegistryHelper _regHelper
        +WindowsInstallerHelper(RegistryHelper regHelper)
        +CleanupUpgradeCodes(Func~string,bool~ shouldDelete)
        +CleanupProducts(Func~string,bool~ shouldDelete)
        +CleanupComponents(Func~string,bool~ shouldDelete)
        +CleanupPublishedComponents(Func~string,bool~ shouldDelete)
    }
    
    class RegistryHelper {
        +EnumerateKeys(hive, subKey)
        +EnumerateValues(hive, subKey)
        +DeleteKey(hive, subKey)
        +DeleteValue(hive, subKey, valueName)
        +GetValue(hive, subKey, valueName)
        +SetValue(hive, subKey, valueName, value, kind)
    }
    
    class GuidHelper {
        +GetExpandedGuid(compressedGuid) string
        +GetCompressedGuid(expandedGuid) string
        +GetDecodedGuid(encodedGuid, out decodedGuid) bool
    }
    
    WindowsInstallerHelper --> RegistryHelper : uses
    WindowsInstallerHelper --> GuidHelper : uses for GUID conversion
Loading

Sources: OfficeScrubC2R-Native.cs:980-1128


Windows Installer Metadata Registry Structure

Windows Installer maintains metadata across multiple registry locations, each using different GUID encoding schemes. The WindowsInstallerHelper class targets four primary metadata areas:

Metadata Type Registry Location GUID Format Scope
UpgradeCodes HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes 32-char compressed Product families
Products HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products
HKCR\Installer\Products
32-char compressed Individual products
Components HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components 32-char compressed (keys and values) Installed components
PublishedComponents HKCR\Installer\Components 20-char base-85 encoded Advertised components
graph TB
    subgraph "HKEY_LOCAL_MACHINE"
        UpgradeCodes["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UpgradeCodes\\<br/>{32-char compressed GUID}"]
        Products["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products\\<br/>{32-char compressed GUID}"]
        Components["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Components\\<br/>{32-char compressed GUID}\\<br/>values: {32-char compressed GUID}"]
    end
    
    subgraph "HKEY_CLASSES_ROOT"
        ProductsHKCR["Installer\\Products\\<br/>{32-char compressed GUID}"]
        ComponentsHKCR["Installer\\Components\\<br/>{32-char compressed GUID}\\<br/>values contain: {20-char base-85}"]
    end
    
    WIH[WindowsInstallerHelper]
    
    WIH -->|CleanupUpgradeCodes| UpgradeCodes
    WIH -->|CleanupProducts| Products
    WIH -->|CleanupProducts| ProductsHKCR
    WIH -->|CleanupComponents| Components
    WIH -->|CleanupPublishedComponents| ComponentsHKCR
Loading

Sources: OfficeScrubC2R-Native.cs:989-1127


GUID Encoding Schemes

Windows Installer uses two distinct GUID encoding schemes, both different from the standard GUID format. The WindowsInstallerHelper relies on GuidHelper for conversion between these formats.

Compressed GUID Format (32 Characters)

Standard GUID format: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} (38 characters)
Compressed format: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (32 characters)

The compression algorithm reverses byte pairs and removes formatting characters:

flowchart LR
    StandardGUID["{90160000-008C-0000-1000-0000000FF1CE}"]
    CompressedGUID["00061000C80000001000000000F1CE"]
    
    StandardGUID -->|"GetCompressedGuid()"| CompressedGUID
    CompressedGUID -->|"GetExpandedGuid()"| StandardGUID
Loading

Base-85 Encoded Format (20 Characters)

PublishedComponents use a 20-character base-85 encoding scheme with a custom decode table. This is the most complex encoding scheme handled by the system.

Sources: OfficeScrubC2R-Native.cs:169-328


Cleanup Operations

CleanupUpgradeCodes

Removes UpgradeCode entries that match the deletion criteria. UpgradeCodes represent product families and are used by Windows Installer to detect related products during upgrades.

sequenceDiagram
    participant Caller
    participant WIH as WindowsInstallerHelper
    participant RH as RegistryHelper
    participant GH as GuidHelper
    
    Caller->>WIH: CleanupUpgradeCodes(shouldDelete)
    WIH->>RH: EnumerateKeys(LocalMachine, "...\\UpgradeCodes")
    RH-->>WIH: ["32charGUID1", "32charGUID2", ...]
    
    loop For each key (32-char compressed)
        WIH->>GH: GetExpandedGuid(key)
        GH-->>WIH: "{GUID-FORMATTED}"
        WIH->>Caller: shouldDelete(expanded GUID)?
        Caller-->>WIH: true/false
        alt Should delete
            WIH->>RH: DeleteKey(LocalMachine, "...\\UpgradeCodes\\{key}")
        end
    end
Loading

Implementation:

Path: SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes
Key format: 32-character compressed GUID
Action: Delete entire key if shouldDelete(expandedGuid) returns true

Sources: OfficeScrubC2R-Native.cs:989-1011


CleanupProducts

Removes Product entries from both HKEY_LOCAL_MACHINE and HKEY_CLASSES_ROOT. Products represent individual MSI packages installed on the system.

flowchart TD
    Start["CleanupProducts(shouldDelete)"]
    
    DefPaths["Define product paths:<br/>1. HKLM\\...\\UserData\\S-1-5-18\\Products<br/>2. HKCR\\Installer\\Products"]
    
    LoopPaths{"For each path"}
    
    DetermineHive{"Path starts<br/>with 'Installer'?"}
    SetHKCR["Set hive = ClassesRoot"]
    SetHKLM["Set hive = LocalMachine"]
    
    EnumKeys["EnumerateKeys(hive, path)"]
    
    LoopKeys{"For each key<br/>(32 chars?)"}
    
    Expand["GetExpandedGuid(key)"]
    
    CheckDelete{"shouldDelete<br/>(expanded)?"}
    
    DeleteKey["DeleteKey(hive, path\\key)"]
    
    Done["Done"]
    
    Start --> DefPaths
    DefPaths --> LoopPaths
    LoopPaths --> DetermineHive
    DetermineHive -->|Yes| SetHKCR
    DetermineHive -->|No| SetHKLM
    SetHKCR --> EnumKeys
    SetHKLM --> EnumKeys
    EnumKeys --> LoopKeys
    LoopKeys -->|Next key| Expand
    Expand --> CheckDelete
    CheckDelete -->|Yes| DeleteKey
    CheckDelete -->|No| LoopKeys
    DeleteKey --> LoopKeys
    LoopKeys -->|No more keys| LoopPaths
    LoopPaths -->|No more paths| Done
Loading

Implementation:

  • Handles two registry locations with different hives
  • Uses compressed GUID format (32 characters)
  • Deletes entire key subtree when match found

Sources: OfficeScrubC2R-Native.cs:1013-1039


CleanupComponents

Removes component values from the Components registry key. Components represent individual files, registry entries, or resources installed by MSI packages.

graph TB
    subgraph "Components Registry Structure"
        CompRoot["HKLM\\SOFTWARE\\...\\Components"]
        CompKey1["{ComponentGUID1}<br/>(32-char compressed)"]
        CompKey2["{ComponentGUID2}<br/>(32-char compressed)"]
        
        CompRoot --> CompKey1
        CompRoot --> CompKey2
        
        CompKey1 --> Val1A["Value: {ProductGUID1}<br/>(32-char compressed)"]
        CompKey1 --> Val1B["Value: {ProductGUID2}<br/>(32-char compressed)"]
        
        CompKey2 --> Val2A["Value: {ProductGUID3}<br/>(32-char compressed)"]
    end
    
    Process["CleanupComponents Process"]
    
    Process -->|"1. Enumerate component keys"| CompRoot
    Process -->|"2. For each component key"| CompKey1
    Process -->|"3. Enumerate values"| Val1A
    Process -->|"4. Expand value name (32-char)"| Val1A
    Process -->|"5. Check shouldDelete(GUID)"| Val1A
    Process -->|"6. DeleteValue if match"| Val1A
Loading

Implementation Details:

  • Component keys are 32-character compressed GUIDs
  • Each component key contains values where:
    • Value name = 32-character compressed product GUID
    • Value data = installation path or other metadata
  • Only deletes individual values, not entire component keys
  • Preserves components used by non-Office products

Sources: OfficeScrubC2R-Native.cs:1041-1067


CleanupPublishedComponents

The most complex cleanup operation, handling PublishedComponents which use 20-character base-85 encoded GUIDs. This method modifies multi-string registry values, removing only the entries that match the deletion criteria.

sequenceDiagram
    participant WIH as WindowsInstallerHelper
    participant RH as RegistryHelper
    participant GH as GuidHelper
    
    Note over WIH: Path: HKCR\Installer\Components
    
    WIH->>RH: EnumerateKeys(ClassesRoot, "Installer\\Components")
    RH-->>WIH: ["32charComp1", "32charComp2", ...]
    
    loop For each component (32-char)
        WIH->>RH: EnumerateValues(ClassesRoot, path\\component)
        RH-->>WIH: ["qualifier1", "qualifier2", ...]
        
        loop For each value
            WIH->>RH: GetValue(ClassesRoot, path\\component, value)
            RH-->>WIH: string[] data
            
            Note over WIH: Create newData list
            
            loop For each item in data
                alt item.Length > 20
                    WIH->>WIH: encoded = item.Substring(0, 20)
                    WIH->>GH: GetDecodedGuid(encoded, out guid)
                    GH-->>WIH: success + guid
                    
                    alt shouldDelete(guid)
                        Note over WIH: Skip item (mark modified)
                    else Keep item
                        Note over WIH: Add to newData
                    end
                end
            end
            
            alt modified == true
                alt newData.Count == 0
                    WIH->>RH: DeleteValue(ClassesRoot, path\\component, value)
                else
                    WIH->>RH: SetValue(ClassesRoot, path\\component, value, newData[], MultiString)
                end
            end
        end
    end
Loading

Key Characteristics:

  • Component keys: 32-character compressed GUIDs
  • Value names: Arbitrary qualifiers
  • Value data: REG_MULTI_SZ containing entries like "[20-char-base85-encoded-guid]>rest-of-data"
  • Requires extracting first 20 characters, decoding to GUID, checking criteria
  • Must preserve other entries in the multi-string value

Sources: OfficeScrubC2R-Native.cs:1069-1127


Integration with OfficeScrubOrchestrator

The WindowsInstallerHelper is instantiated by the OfficeScrubOrchestrator class and accessed through its WindowsInstaller property:

graph LR
    Orchestrator["OfficeScrubOrchestrator"]
    WIH["WindowsInstallerHelper"]
    RH["RegistryHelper"]
    
    Orchestrator -->|"Creates with Registry"| WIH
    Orchestrator -->|"Property: WindowsInstaller"| WIH
    WIH -->|"Constructor parameter"| RH
    WIH -->|"Uses _regHelper"| RH
Loading

Usage Pattern:

1. OfficeScrubOrchestrator initialization creates RegistryHelper
2. RegistryHelper passed to WindowsInstallerHelper constructor
3. Orchestrator exposes WindowsInstallerHelper via WindowsInstaller property
4. PowerShell layer calls cleanup methods with shouldDelete predicate

Sources: OfficeScrubC2R-Native.cs:1349-1370, OfficeScrubC2R-Native.cs:980-987


Method Signatures and Parameters

Common Pattern: Predicate-Based Deletion

All cleanup methods accept a Func<string, bool> delegate that determines whether a specific GUID should be deleted:

Method Parameter Return Type
CleanupUpgradeCodes Func<string, bool> shouldDelete void
CleanupProducts Func<string, bool> shouldDelete void
CleanupComponents Func<string, bool> shouldDelete void
CleanupPublishedComponents Func<string, bool> shouldDelete void

Predicate Function:

  • Input: Expanded GUID in standard format (e.g., "{90160000-008C-0000-1000-0000000FF1CE}")
  • Output: true to delete, false to preserve
  • Called by PowerShell layer with Office-specific detection logic

Sources: OfficeScrubC2R-Native.cs:989-1127


GUID Format Conversion Flow

The WindowsInstallerHelper performs extensive GUID format conversions to normalize different encoding schemes before applying deletion criteria:

flowchart TD
    subgraph "Input Formats"
        Compressed32["32-char Compressed<br/>00061000C80000001000000000F1CE"]
        Base8520["20-char Base-85<br/>!Y7AcWZ9_^~`'T-pG"]
    end
    
    subgraph "Conversion Layer"
        GetExpanded["GuidHelper.GetExpandedGuid()"]
        GetDecoded["GuidHelper.GetDecodedGuid()"]
    end
    
    subgraph "Standard Format"
        StandardGUID["{90160000-008C-0000-1000-0000000FF1CE}"]
    end
    
    subgraph "Decision Layer"
        Predicate["shouldDelete(standardGUID)"]
    end
    
    subgraph "Registry Actions"
        DeleteKey["DeleteKey()"]
        DeleteValue["DeleteValue()"]
        SetValue["SetValue() with filtered list"]
    end
    
    Compressed32 --> GetExpanded
    Base8520 --> GetDecoded
    
    GetExpanded --> StandardGUID
    GetDecoded --> StandardGUID
    
    StandardGUID --> Predicate
    
    Predicate -->|"true"| DeleteKey
    Predicate -->|"true"| DeleteValue
    Predicate -->|"true"| SetValue
Loading

Conversion Requirements:

  • UpgradeCodes: 32-char → Expanded GUID
  • Products: 32-char → Expanded GUID
  • Components: 32-char (keys and values) → Expanded GUID
  • PublishedComponents: 32-char (keys), 20-char base-85 (value data) → Expanded GUID

Sources: OfficeScrubC2R-Native.cs:169-328, OfficeScrubC2R-Native.cs:989-1127


Error Handling and Defensive Programming

The WindowsInstallerHelper follows the codebase's "silent failure" pattern, where operations that fail do not throw exceptions but continue processing remaining items:

Defensive Checks:

Check Location Purpose
key.Length == 32 All cleanup methods Validate compressed GUID format
guid != null After GetExpandedGuid() Verify conversion succeeded
item.Length > 20 CleanupPublishedComponents Validate base-85 encoded data
GetDecodedGuid(encoded, out guid) CleanupPublishedComponents Verify decoding succeeded

Error Handling Strategy:

  • Invalid GUID formats are skipped without error
  • Registry access failures in RegistryHelper are caught internally
  • Method continues processing subsequent items after individual failures
  • No exceptions propagated to caller

Sources: OfficeScrubC2R-Native.cs:1001-1126, docs/memory-bank/guidelines.md:24-38


Performance Characteristics

The WindowsInstallerHelper operates efficiently through several optimization strategies:

Performance Features:

  1. HashSet-based enumeration in RegistryHelper - O(1) duplicate detection
  2. Single-pass processing - Each registry location enumerated once
  3. Lazy evaluation - Only converts GUIDs that exist in registry
  4. In-place modification - PublishedComponents modifies multi-string values without full reconstruction

Typical Execution Context:

  • Called during Stage 1 (Uninstall) of the three-stage pipeline
  • Runs after C2R uninstallation but before file cleanup
  • Operates on relatively small dataset (dozens to hundreds of keys)
  • Execution time typically under 1 second per cleanup method

Sources: OfficeScrubC2R-Native.cs:989-1127, docs/memory-bank/guidelines.md:87-103


Usage Example from PowerShell Layer

The WindowsInstallerHelper is called from the PowerShell utilities layer with Office-specific predicates:

Typical call pattern:
1. PowerShell detects Office product GUIDs during Stage 0 (Detection)
2. Builds list/hashset of Office product GUIDs to remove
3. Calls WindowsInstaller cleanup methods with predicate function:
   
   $orchestrator.WindowsInstaller.CleanupProducts({
       param($guid)
       return $officeProductGuids.Contains($guid)
   })
   
4. WindowsInstallerHelper enumerates registry, expands GUIDs, applies predicate
5. Deletes matching entries

Integration Points:

  • Created during OfficeScrubOrchestrator initialization with RegistryHelper dependency
  • Accessed via $orchestrator.WindowsInstaller property from PowerShell
  • Predicate functions provided by PowerShell utilities layer
  • No direct file system or process interaction

Sources: OfficeScrubC2R-Native.cs:1349-1370, OfficeScrubC2R-Native.cs:980-1128


Related Components

For complete understanding of the Windows Installer cleanup subsystem, refer to:

Clone this wiki locally