Repository navigation
GUID Transformation System
Relevant source files
The following files were used as context for generating this wiki page:
The GUID Transformation System provides bidirectional conversion between the three GUID encoding formats used by Windows Installer metadata. This system is critical for identifying and cleaning Office-related registry entries during the Click-to-Run uninstallation process.
For information about the broader Windows Installer cleanup operations that consume these transformations, see WindowsInstallerHelper.
Sources: OfficeScrubC2R-Native.cs:169-329
Windows Installer stores product, component, and upgrade code GUIDs in three distinct formats depending on registry location and data type.
| Format | Length | Example | Usage Location |
|---|---|---|---|
| Expanded | 38 chars | {90140000-0011-0000-0000-0000000FF1CE} |
Standard GUID representation |
| Compressed | 32 chars | 00004109110000000000000F1CE00000 |
Registry key names (Products, UpgradeCodes, Components) |
| Encoded | 20 chars | QD3H=4cB#zG1aD6;R4 |
Multi-string registry values (PublishedComponents) |
The compressed format applies byte-pair reversals to specific GUID segments:
flowchart LR
subgraph Expanded["Expanded Format"]
E1["Segment 1<br/>8 chars"]
E2["Segment 2<br/>4 chars"]
E3["Segment 3<br/>4 chars"]
E4["Segment 4<br/>4 chars"]
E5["Segment 5<br/>12 chars"]
end
subgraph Compressed["Compressed Format"]
C1["Reversed<br/>8 chars"]
C2["Reversed<br/>4 chars"]
C3["Reversed<br/>4 chars"]
C4["Pair-swapped<br/>4 chars"]
C5["Pair-swapped<br/>12 chars"]
end
E1 -->|"Reverse entire segment"| C1
E2 -->|"Reverse entire segment"| C2
E3 -->|"Reverse entire segment"| C3
E4 -->|"Swap each pair"| C4
E5 -->|"Swap each pair"| C5
Sources: OfficeScrubC2R-Native.cs:173-214, OfficeScrubC2R-Native.cs:216-253
The GuidHelper static class in the OfficeScrubNative namespace provides three transformation methods and one helper method.
classDiagram
class GuidHelper {
<<static>>
+GetExpandedGuid(compressedGuid: string) string
+GetCompressedGuid(expandedGuid: string) string
+GetDecodedGuid(encodedGuid: string, out decodedGuid: string) bool
-Reverse(s: string) string
}
class WindowsInstallerHelper {
-_regHelper: RegistryHelper
+CleanupUpgradeCodes(shouldDelete: Func)
+CleanupProducts(shouldDelete: Func)
+CleanupComponents(shouldDelete: Func)
+CleanupPublishedComponents(shouldDelete: Func)
}
WindowsInstallerHelper ..> GuidHelper : uses
Sources: OfficeScrubC2R-Native.cs:169-329, OfficeScrubC2R-Native.cs:978-1128
Converts a 32-character compressed GUID to the standard 38-character expanded format with braces and dashes.
Algorithm:
- Validate input length is exactly 32 characters
- Allocate
StringBuilderwith capacity 38 - Reverse bytes in segments 1, 2, and 3 (positions 0-8, 8-12, 12-16)
- Swap byte pairs in segments 4 and 5 (positions 16-20, 20-32)
- Insert braces and dashes at positions:
{[0]-[1]-[2]-[3]-[4]} - Return uppercase result
flowchart TD
Start["GetExpandedGuid(compressedGuid)"] --> Validate{"Length == 32?"}
Validate -->|No| ReturnNull["return null"]
Validate -->|Yes| AllocSB["StringBuilder sb = new(38)"]
AllocSB --> AppendBrace["sb.Append('{')"]
AppendBrace --> Rev1["Reverse(substring 0-8)"]
Rev1 --> Dash1["sb.Append('-')"]
Dash1 --> Rev2["Reverse(substring 8-12)"]
Rev2 --> Dash2["sb.Append('-')"]
Dash2 --> Rev3["Reverse(substring 12-16)"]
Rev3 --> Dash3["sb.Append('-')"]
Dash3 --> Swap1["Swap pairs 16-20"]
Swap1 --> Dash4["sb.Append('-')"]
Dash4 --> Swap2["Swap pairs 20-32"]
Swap2 --> CloseBrace["sb.Append('}')"]
CloseBrace --> Upper["ToUpperInvariant()"]
Upper --> Return["return result"]
ReturnNull --> End["End"]
Return --> End
Sources: OfficeScrubC2R-Native.cs:173-214
Converts a 38-character expanded GUID to the 32-character compressed format used in registry key names.
Algorithm:
- Validate input length is exactly 38 characters
- Trim braces and split on dash delimiter (5 segments)
- Reverse segments 1, 2, and 3
- Swap byte pairs in segments 4 and 5
- Concatenate all segments without delimiters
- Return uppercase result
Sources: OfficeScrubC2R-Native.cs:216-253
Decodes a 20-character Base85-encoded GUID to expanded format. This encoding is used exclusively in the PublishedComponents registry location.
Algorithm:
- Validate input length is exactly 20 characters
- Process in groups of 5 characters (4 groups total)
- For each group:
- Look up each character in the Base85 decode table
- Accumulate as
total += table[char] * (85^position) - After 5 characters, append
total.ToString("X8")to result
- Rearrange decoded hex segments to standard GUID format
- Return formatted result with braces and dashes
Base85 Decode Table:
The decode table maps ASCII characters to 5-bit values (0-84). Invalid characters are marked with 0xff.
flowchart TD
Start["GetDecodedGuid(encodedGuid, out decodedGuid)"] --> Validate{"Length == 20?"}
Validate -->|No| SetNull["decodedGuid = null<br/>return false"]
Validate -->|Yes| InitSB["StringBuilder sb = new()"]
InitSB --> LoopStart["For i = 0 to 19"]
LoopStart --> CheckGroup{"i % 5 == 0?"}
CheckGroup -->|Yes| ResetAccum["total = 0<br/>pow85 = 1"]
CheckGroup -->|No| GetChar["Get char at i"]
ResetAccum --> GetChar
GetChar --> Lookup["chr = table[ascii]"]
Lookup --> ValidChar{"chr != 0xff?"}
ValidChar -->|No| SetNull
ValidChar -->|Yes| Accumulate["total += chr * pow85"]
Accumulate --> CheckEnd{"i % 5 == 4?"}
CheckEnd -->|Yes| AppendHex["sb.Append(total.ToString('X8'))"]
CheckEnd -->|No| NextPow["pow85 *= 85"]
AppendHex --> NextIter
NextPow --> NextIter["i++"]
NextIter --> LoopEnd{"i < 20?"}
LoopEnd -->|Yes| LoopStart
LoopEnd -->|No| Rearrange["Rearrange segments<br/>to GUID format"]
Rearrange --> FormatGuid["Format as<br/>{seg0-seg1-seg2-seg3-seg4}"]
FormatGuid --> Success["return true"]
SetNull --> End["End"]
Success --> End
Sources: OfficeScrubC2R-Native.cs:255-320
The WindowsInstallerHelper class consumes GUID transformations to identify Office-related metadata across four registry locations.
flowchart TD
subgraph Registry["Windows Registry Locations"]
UC["UpgradeCodes<br/>HKLM\SOFTWARE\...\UpgradeCodes<br/>32-char compressed keys"]
PR["Products<br/>HKLM\...\Products<br/>HKCR\Installer\Products<br/>32-char compressed keys"]
CM["Components<br/>HKLM\...\Components<br/>32-char keys + 32-char values"]
PC["PublishedComponents<br/>HKCR\Installer\Components<br/>32-char keys + 20-char encoded in MultiString"]
end
subgraph GuidHelper["GuidHelper Methods"]
GE["GetExpandedGuid()"]
GD["GetDecodedGuid()"]
end
subgraph WindowsInstallerHelper["WindowsInstallerHelper Methods"]
CUC["CleanupUpgradeCodes()"]
CP["CleanupProducts()"]
CC["CleanupComponents()"]
CPC["CleanupPublishedComponents()"]
end
UC --> CUC
PR --> CP
CM --> CC
PC --> CPC
CUC --> GE
CP --> GE
CC --> GE
CPC --> GD
GE --> IsInScope{"OfficeScrubOrchestrator<br/>IsInScope()"}
GD --> IsInScope
IsInScope -->|"true"| Delete["Delete registry entry"]
IsInScope -->|"false"| Keep["Preserve entry"]
Sources: OfficeScrubC2R-Native.cs:978-1128
Each cleanup method follows a similar pattern: enumerate keys, transform GUIDs, validate scope, and delete if matched.
sequenceDiagram
participant WIH as WindowsInstallerHelper
participant RH as RegistryHelper
participant GH as GuidHelper
participant Orch as OfficeScrubOrchestrator
WIH->>RH: EnumerateKeys(HKLM, "...\UpgradeCodes")
RH-->>WIH: string[] keys (32-char compressed)
loop For each key
WIH->>WIH: Check key.Length == 32
WIH->>GH: GetExpandedGuid(key)
GH-->>WIH: expandedGuid (38-char)
alt expandedGuid != null
WIH->>Orch: shouldDelete(expandedGuid)
Orch-->>WIH: bool result
alt result == true
WIH->>RH: DeleteKey(HKLM, "...\UpgradeCodes\{key}")
end
end
end
Sources: OfficeScrubC2R-Native.cs:989-1011
This method handles the most complex encoding: Base85-encoded GUIDs embedded in the first 20 characters of multi-string registry values.
sequenceDiagram
participant WIH as WindowsInstallerHelper
participant RH as RegistryHelper
participant GH as GuidHelper
participant Orch as OfficeScrubOrchestrator
WIH->>RH: EnumerateKeys(HKCR, "Installer\Components")
RH-->>WIH: string[] componentKeys
loop For each componentKey
WIH->>RH: EnumerateValues(HKCR, "Installer\Components\{key}")
RH-->>WIH: string[] valueNames
loop For each valueName
WIH->>RH: GetValue(HKCR, path, valueName)
RH-->>WIH: string[] data (MultiString)
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: bool success + decodedGuid
alt success == true
WIH->>Orch: shouldDelete(decodedGuid)
Orch-->>WIH: bool result
alt result == true
WIH->>WIH: Mark item for removal
else result == false
WIH->>WIH: Keep item in newData
end
end
end
end
alt modified == true
alt newData.Count == 0
WIH->>RH: DeleteValue(HKCR, path, valueName)
else
WIH->>RH: SetValue(HKCR, path, valueName, newData)
end
end
end
end
Sources: OfficeScrubC2R-Native.cs:1069-1127
The optimized implementation in the reference file demonstrates several performance enhancements for high-volume GUID transformations.
| Optimization | Original Approach | Optimized Approach | Benefit |
|---|---|---|---|
| Decode Table | Allocates array per call | Static readonly byte[128]
|
Eliminates 255+ allocations per call |
| String Building |
StringBuilder heap allocation |
Span<char> stack allocation |
Zero heap allocations |
| Character Reversal |
Array.Reverse() + new string()
|
ReverseToSpan() in-place |
Reduces allocations by 50% |
| Format Conversion | StringBuilder.Append() |
TryFormat() to Span<char>
|
Reduces string concatenations |
flowchart LR
subgraph Original["Original Implementation"]
O1["StringBuilder sb = new(38)"]
O2["Multiple Append() calls"]
O3["String concatenation"]
O4["Array.Reverse()"]
end
subgraph Optimized["Optimized Implementation"]
P1["Span<char> result = stackalloc char[38]"]
P2["Direct index assignment"]
P3["ReverseToSpan()"]
P4["new string(result)"]
end
Original -->|"Heap allocations<br/>StringBuilder overhead"| Perf1["~150ns per call"]
Optimized -->|"Stack only<br/>Zero allocations"| Perf2["~80ns per call"]
Sources: docs/OfficeScrubC2R-Native-Optimized-Real.cs:174-225, docs/OfficeScrubC2R-Native-Optimized-Real.cs:263-318
All transformation methods implement defensive validation with graceful failure modes.
| Method | Input Validation | Error Behavior |
|---|---|---|
GetExpandedGuid |
length == 32 |
Returns null
|
GetCompressedGuid |
length == 38 |
Returns null
|
GetDecodedGuid |
length == 20 + valid ASCII |
Sets out param to null, returns false
|
All methods wrap transformation logic in try-catch blocks to handle unexpected input data without crashing the cleanup process.
flowchart TD
Input["Receive GUID string"] --> TryBlock["try {"]
TryBlock --> Validate["Validate length"]
Validate --> Transform["Apply transformation"]
Transform --> Return["Return result"]
Return --> End["End"]
TryBlock -.->|"Any exception"| CatchBlock["catch { }"]
CatchBlock --> ReturnNull["Return null/false"]
ReturnNull --> End
Sources: OfficeScrubC2R-Native.cs:178-213, OfficeScrubC2R-Native.cs:221-252, OfficeScrubC2R-Native.cs:262-319
The following table maps GUID formats to their storage locations in the Windows Registry.
| Registry Path | Hive | Key Format | Value Format | Used By |
|---|---|---|---|---|
SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes |
HKLM |
32-char compressed | - | CleanupUpgradeCodes |
SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products |
HKLM |
32-char compressed | - | CleanupProducts |
Installer\Products |
HKCR |
32-char compressed | - | CleanupProducts |
SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components |
HKLM |
32-char compressed | 32-char compressed | CleanupComponents |
Installer\Components |
HKCR |
32-char compressed | 20-char encoded (MultiString) | CleanupPublishedComponents |
Sources: OfficeScrubC2R-Native.cs:989-1127
The OfficeConstants class defines GUID length constants used throughout the transformation system.
classDiagram
class OfficeConstants {
<<static>>
+OFFICE_ID: string = "{0000000FF1CE}"
+PROD_LEN: int = 13
+SQUISHED: int = 20
+COMPRESSED: int = 32
}
class GuidHelper {
<<static>>
}
GuidHelper ..> OfficeConstants : uses SQUISHED, COMPRESSED
| Constant | Value | Purpose |
|---|---|---|
SQUISHED |
20 | Length of Base85-encoded GUIDs |
COMPRESSED |
32 | Length of compressed (no dashes) GUIDs |
| Standard Expanded | 38 | Length of formatted GUIDs with braces and dashes |
Sources: OfficeScrubC2R-Native.cs:29-56