Repository navigation
WindowsInstallerHelper
Relevant source files
The following files were used as context for generating this wiki page:
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.
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
Sources: OfficeScrubC2R-Native.cs:980-1128
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\ProductsHKCR\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
Sources: OfficeScrubC2R-Native.cs:989-1127
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.
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
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
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
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
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
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
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
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
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
Key Characteristics:
- Component keys: 32-character compressed GUIDs
- Value names: Arbitrary qualifiers
- Value data:
REG_MULTI_SZcontaining 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
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
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
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:
trueto delete,falseto preserve - Called by PowerShell layer with Office-specific detection logic
Sources: OfficeScrubC2R-Native.cs:989-1127
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
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
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
RegistryHelperare 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
The WindowsInstallerHelper operates efficiently through several optimization strategies:
Performance Features:
- HashSet-based enumeration in RegistryHelper - O(1) duplicate detection
- Single-pass processing - Each registry location enumerated once
- Lazy evaluation - Only converts GUIDs that exist in registry
- 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
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
OfficeScrubOrchestratorinitialization withRegistryHelperdependency - Accessed via
$orchestrator.WindowsInstallerproperty 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
For complete understanding of the Windows Installer cleanup subsystem, refer to:
- GuidHelper - GUID encoding/decoding utilities used extensively by this class
- RegistryHelper - Underlying registry operations (enumerate, delete, get/set values)
- Native Library Overview - How WindowsInstallerHelper fits into the orchestrator pattern
- Three-Stage Execution Pipeline - When MSI cleanup occurs in the overall removal process