Repository navigation
Architecture
Relevant source files
The following files were used as context for generating this wiki page:
This document provides a comprehensive overview of the OfficeScrubC2R system architecture, explaining the hybrid PowerShell/C# design, component relationships, and key architectural patterns. It covers the technical structure of the codebase, how the layers interact, and the design decisions that enable 10-50x performance improvements over the original VBScript implementation.
For practical usage information, see User Guide. For building and contributing, see Developer Guide. For detailed component reference, see Native Performance Layer.
The OfficeScrubC2R system employs a three-layer hybrid architecture combining PowerShell orchestration with high-performance C# native operations and direct Win32 API access.
Architecture: Three-Layer Hybrid Design
graph TB
subgraph "Layer 1: PowerShell Module Layer"
PSD1["OfficeScrubC2R.psd1<br/>Module Manifest"]
PSM1["OfficeScrubC2R.psm1<br/>Main Module"]
UTIL["OfficeScrubC2R-Utilities.psm1<br/>Nested Module"]
end
subgraph "Layer 2: Native C# Performance Layer"
DLL["OfficeScrubNative.dll"]
ORCH["OfficeScrubOrchestrator"]
subgraph "Helper Classes"
REG["RegistryHelper"]
FILE["FileHelper"]
PROC["ProcessHelper"]
WIH["WindowsInstallerHelper"]
TLH["TypeLibHelper"]
LH["LicenseHelper"]
SVC["ServiceHelper"]
SH["ShellHelper"]
GUID["GuidHelper"]
end
end
subgraph "Layer 3: Win32 API Integration"
NATIVE["NativeMethods<br/>P/Invoke Declarations"]
subgraph "Windows APIs"
ADVAPI["advapi32.dll<br/>Registry APIs"]
KERNEL["kernel32.dll<br/>File System APIs"]
SHELL["shell32.dll<br/>Shell APIs"]
WMI["WMI<br/>System.Management"]
end
end
PSD1 --> PSM1
PSM1 --> UTIL
PSM1 --> DLL
DLL --> ORCH
ORCH --> REG
ORCH --> FILE
ORCH --> PROC
ORCH --> WIH
ORCH --> TLH
ORCH --> LH
ORCH --> SVC
ORCH --> SH
REG --> NATIVE
FILE --> NATIVE
PROC --> WMI
WIH --> REG
TLH --> REG
LH --> WMI
SVC --> WMI
SH --> SHELL
NATIVE --> ADVAPI
NATIVE --> KERNEL
NATIVE --> SHELL
Sources: OfficeScrubC2R-Native.cs:1-2000, OfficeScrubC2R.psm1:1-500, docs/memory-bank/structure.md:60-106
The system uses a hybrid PowerShell/C# architecture to balance ease of use with high performance. This design decision enables the tool to maintain a familiar PowerShell interface while delegating performance-critical operations to compiled native code.
| Aspect | PowerShell Layer | Native C# Layer |
|---|---|---|
| Purpose | User interface, workflow orchestration, compatibility | High-performance operations, Win32 API access |
| Performance | Adequate for logic and control flow | 10-50x faster for registry/file operations |
| Maintainability | Easy to modify, script-based | Compiled, requires build step |
| Cross-platform | PowerShell 5.1+ and Core 7+ | .NET Framework 4.5+ / .NET Core 7+ |
The PowerShell module implements a smart loading strategy with automatic fallback:
-
Primary: Load pre-compiled
OfficeScrubNative.dll -
Fallback: Compile
OfficeScrubC2R-Native.cson-demand if DLL is missing or incompatible - Verification: Validate assembly loading across PowerShell editions
This ensures the module can function even if the DLL is missing, corrupted, or incompatible with the current runtime.
Sources: OfficeScrubC2R.psm1:1-200, docs/memory-bank/structure.md:80-93, docs/memory-bank/tech.md:1-50
The PowerShell layer consists of three files that provide the user interface and module definition.
PowerShell Module Structure
graph LR
subgraph "Module Files"
PSD1["OfficeScrubC2R.psd1"]
PSM1["OfficeScrubC2R.psm1"]
UTIL["OfficeScrubC2R-Utilities.psm1"]
end
subgraph "Exported Functions"
INVOKE["Invoke-OfficeScrubC2R"]
IMPORT["Import-OfficeScrubNative"]
NEW["New-OfficeScrubOrchestrator"]
TEST_PATH["Test-OfficeC2RPath"]
TEST_SCOPE["Test-OfficeProductScope"]
GET["Get-OfficeScrubHelpers"]
end
PSD1 -->|"Defines"| PSM1
PSM1 -->|"Imports"| UTIL
PSM1 -->|"Exports"| INVOKE
PSM1 -->|"Exports"| IMPORT
PSM1 -->|"Exports"| NEW
PSM1 -->|"Exports"| TEST_PATH
PSM1 -->|"Exports"| TEST_SCOPE
PSM1 -->|"Exports"| GET
Defines module metadata, dependencies, and exports. Key properties:
-
ModuleVersion: Semantic version tracking -
GUID: Unique module identifier -
NestedModules: ReferencesOfficeScrubC2R-Utilities.psm1 -
RequiredAssemblies: SpecifiesOfficeScrubNative.dll -
FunctionsToExport: Public API surface
Contains the core PowerShell functions:
- Import-OfficeScrubNative: Loads the native DLL with fallback compilation
-
New-OfficeScrubOrchestrator: Creates an
OfficeScrubOrchestratorinstance - Test-OfficeC2RPath: Validates if a path matches Click-to-Run patterns
- Test-OfficeProductScope: Checks if a product GUID is in scope for cleanup
- Get-OfficeScrubHelpers: Returns helper instances for advanced scenarios
Provides utility functions for logging, path manipulation, and helper operations used internally by the main module.
Sources: OfficeScrubC2R.psd1:1-100, OfficeScrubC2R.psm1:1-500, docs/memory-bank/structure.md:62-68
The native C# layer is implemented in a single file, OfficeScrubC2R-Native.cs, compiled into OfficeScrubNative.dll. This layer provides high-performance operations through specialized helper classes coordinated by the OfficeScrubOrchestrator.
Native Layer Class Hierarchy
graph TB
subgraph "Namespace: OfficeScrubNative"
ORCH["OfficeScrubOrchestrator<br/>Line 1315"]
subgraph "Helper Classes"
REG["RegistryHelper<br/>Line 334"]
FILE["FileHelper<br/>Line 622"]
PROC["ProcessHelper<br/>Line 773"]
WIH["WindowsInstallerHelper<br/>Line 980"]
TLH["TypeLibHelper<br/>Line 1101"]
LH["LicenseHelper<br/>Line 1182"]
SVC["ServiceHelper<br/>Line 1245"]
SH["ShellHelper<br/>Line 864"]
end
subgraph "Utility Classes"
GUID["GuidHelper<br/>Line 171<br/>(static)"]
CONST["OfficeConstants<br/>Line 29<br/>(static)"]
NATIVE["NativeMethods<br/>Line 62<br/>(internal static)"]
end
end
ORCH -->|"Creates/Owns"| REG
ORCH -->|"Creates/Owns"| FILE
ORCH -->|"Creates/Owns"| PROC
ORCH -->|"Creates/Owns"| WIH
ORCH -->|"Creates/Owns"| TLH
ORCH -->|"Creates/Owns"| LH
ORCH -->|"Creates/Owns"| SVC
ORCH -->|"Creates/Owns"| SH
WIH -->|"Uses"| REG
WIH -->|"Uses"| GUID
TLH -->|"Uses"| REG
LH -->|"Uses"| FILE
REG -->|"P/Invoke"| NATIVE
FILE -->|"P/Invoke"| NATIVE
SH -->|"COM"| NATIVE
The central coordinator class that instantiates and exposes all helper classes. Located at OfficeScrubC2R-Native.cs:1315-1383, it provides:
-
Properties: Public accessors to all helper instances (
Registry,Files,Processes, etc.) -
Factory Constructor: Takes
is64Bitparameter to enable WOW64 support -
Validation Methods:
-
IsC2RPath(string path): Checks if path contains Click-to-Run patterns -
IsInScope(string productCode): Validates Office product GUIDs for cleanup eligibility
-
Each helper class encapsulates operations for a specific Windows subsystem:
| Helper Class | Purpose | Key Methods | Line Range |
|---|---|---|---|
RegistryHelper |
Registry operations with WOW64 support |
KeyExists, DeleteKey, EnumerateKeys, GetValue, SetValue
|
334-616 |
FileHelper |
File system operations with reboot scheduling |
DeleteFile, DeleteDirectory, ScheduleDeleteOnReboot
|
622-767 |
ProcessHelper |
Process management with parallel execution |
TerminateProcesses, GetProcessesUsingPath, IsProcessRunning
|
773-858 |
WindowsInstallerHelper |
MSI metadata cleanup |
CleanupUpgradeCodes, CleanupProducts, CleanupComponents
|
980-1095 |
TypeLibHelper |
COM type library cleanup | CleanupKnownTypeLibs |
1101-1176 |
LicenseHelper |
Office license and SPP operations |
CleanOSPP, ClearVNextLicenseCache
|
1182-1239 |
ServiceHelper |
Windows service management | DeleteService |
1245-1309 |
ShellHelper |
Shell integration cleanup |
UnpinFromTaskbar, UnpinFromStartMenu, RestartExplorer
|
864-974 |
Static utility classes provide shared functionality:
- GuidHelper 171-328: GUID encoding/decoding for Windows Installer metadata (compressed, expanded, Base85 encoded formats)
-
OfficeConstants 29-56: Defines constants like
OFFICE_ID,C2R_PATTERNS,OFFICE_PROCESSES - NativeMethods 62-153: P/Invoke declarations for Win32 APIs (internal use only)
Sources: OfficeScrubC2R-Native.cs:1-2000, docs/memory-bank/structure.md:69-78, docs/memory-bank/guidelines.md:44-68
The NativeMethods class OfficeScrubC2R-Native.cs:62-153 provides P/Invoke declarations for direct Win32 API access.
Win32 API Integration Points
graph LR
subgraph "Helper Classes"
REG["RegistryHelper"]
FILE["FileHelper"]
SH["ShellHelper"]
end
subgraph "NativeMethods P/Invoke"
REGAPI["RegOpenKeyEx<br/>RegDeleteKeyEx<br/>RegEnumKeyEx<br/>RegCloseKey"]
FILEAPI["MoveFileEx<br/>SetFileAttributes<br/>RemoveDirectory"]
SHELLAPI["ILCreateFromPath<br/>SHCreateShellItem<br/>ILFree"]
end
subgraph "Windows DLLs"
ADVAPI["advapi32.dll"]
KERNEL["kernel32.dll"]
SHELL["shell32.dll"]
end
REG -->|"Calls"| REGAPI
FILE -->|"Calls"| FILEAPI
SH -->|"Calls"| SHELLAPI
REGAPI -->|"P/Invoke"| ADVAPI
FILEAPI -->|"P/Invoke"| KERNEL
SHELLAPI -->|"P/Invoke"| SHELL
Used by RegistryHelper for low-level registry operations:
-
RegOpenKeyEx: Opens registry keys with specific access rights (KEY_WOW64_64KEY, KEY_WOW64_32KEY) -
RegDeleteKeyEx: Deletes registry keys with WOW64 view specification -
RegEnumKeyEx/RegEnumValue: Enumerates subkeys and values -
RegCloseKey: Closes registry key handles
Used by FileHelper for file operations:
-
MoveFileEx: Schedules file deletion on reboot withMOVEFILE_DELAY_UNTIL_REBOOTflag -
SetFileAttributes: Modifies file attributes (e.g., removing read-only) -
RemoveDirectory: Directly removes directories
Used by ShellHelper for shell integration:
-
ILCreateFromPath: Creates an item identifier list from a file path -
SHCreateShellItem: Creates a shell item from an identifier -
ILFree: Frees shell item memory
Several helper classes use System.Management for WMI operations:
-
LicenseHelper: QueriesSoftwareLicensingProduct/OfficeSoftwareProtectionProduct1186-1218 -
ServiceHelper: QueriesWin32_Servicefor service management 1251-1280
Sources: OfficeScrubC2R-Native.cs:62-165, docs/memory-bank/tech.md:82-95, docs/memory-bank/guidelines.md:52-56
The cleanup process follows a three-stage architecture inherited from the original VBScript implementation, ensuring systematic removal of Office components.
Three-Stage Execution Flow
graph TB
START["Invoke-OfficeScrubC2R"]
subgraph "Stage 1: Uninstall Preparation"
S1_LIC["LicenseHelper.CleanOSPP<br/>LicenseHelper.ClearVNextLicenseCache"]
S1_PROC["ProcessHelper.TerminateProcesses<br/>OFFICE_PROCESSES array"]
S1_TASK["Delete Scheduled Tasks<br/>Office-related tasks"]
S1_SHELL["ShellHelper.UnpinFromTaskbar<br/>ShellHelper.UnpinFromStartMenu"]
S1_ODT["Attempt ODT/MSI Uninstall"]
end
subgraph "Stage 2: Registry Cleanup"
S2_HKCU["RegistryHelper.DeleteKey<br/>HKCU Office Keys"]
S2_HKLM["RegistryHelper.DeleteKey<br/>HKLM Office Keys"]
S2_MSI["WindowsInstallerHelper.CleanupUpgradeCodes<br/>CleanupProducts<br/>CleanupComponents"]
S2_TL["TypeLibHelper.CleanupKnownTypeLibs"]
S2_SHELL["Delete Shell Integration Keys"]
end
subgraph "Stage 3: File System Cleanup"
S3_SVC["ServiceHelper.DeleteService<br/>ClickToRun services"]
S3_FILES["FileHelper.DeleteFile<br/>FileHelper.DeleteDirectory"]
S3_REBOOT["FileHelper.ScheduleDeleteOnReboot<br/>For locked files"]
end
START --> S1_LIC
S1_LIC --> S1_PROC
S1_PROC --> S1_TASK
S1_TASK --> S1_SHELL
S1_SHELL --> S1_ODT
S1_ODT --> S2_HKCU
S2_HKCU --> S2_HKLM
S2_HKLM --> S2_MSI
S2_MSI --> S2_TL
S2_TL --> S2_SHELL
S2_SHELL --> S3_SVC
S3_SVC --> S3_FILES
S3_FILES --> S3_REBOOT
S3_REBOOT --> END["Return Exit Code"]
Objective: Prepare the system for cleanup by removing licenses, terminating processes, and attempting graceful uninstall.
-
License Cleanup:
LicenseHelper.CleanOSPP()removes Software Protection Platform keys 1184-1219 -
Process Termination:
ProcessHelper.TerminateProcesses()kills Office processes defined inOfficeConstants.OFFICE_PROCESSES47-55 - Task Removal: Scheduled tasks are deleted
-
Shell Cleanup: Shortcuts are unpinned using
ShellHelper.UnpinFromTaskbar()andUnpinFromStartMenu()866-947 - ODT/MSI Uninstall: Attempt standard uninstallation if applicable
Objective: Remove all registry artifacts across user and machine hives, including Windows Installer metadata.
-
User Registry: Delete keys under
HKEY_CURRENT_USER\Software\Microsoft\Office -
Machine Registry: Delete keys under
HKEY_LOCAL_MACHINE\Software\Microsoft\Office - Windows Installer:
-
COM Type Libraries:
TypeLibHelper.CleanupKnownTypeLibs()1125-1174 - Shell Integration: Remove file associations and context menu handlers
Objective: Delete Office files, folders, and services, with fallback to reboot scheduling.
-
Service Deletion:
ServiceHelper.DeleteService()removes ClickToRun services 1247-1308 -
File Deletion:
FileHelper.DeleteFile()removes individual files 626-651 -
Directory Deletion:
FileHelper.DeleteDirectory()removes folders usingcmd.exe rd /s /qfor performance 653-686 -
Reboot Scheduling: Locked files are scheduled for deletion via
FileHelper.ScheduleDeleteOnReboot()usingMoveFileExAPI 689-697
Sources: OfficeScrubC2R-Native.cs:1315-2000, docs/memory-bank/product.md:14-41
On 64-bit Windows systems, the registry has separate views for 32-bit and 64-bit applications. Office can install components in either view, requiring dual-path cleanup.
WOW64 Registry Architecture
graph TB
HELPER["RegistryHelper<br/>Constructor(is64Bit)"]
subgraph "64-bit System Detection"
CHECK["_is64Bit field<br/>Line 336"]
end
subgraph "Registry Operations"
OP["KeyExists / DeleteKey /<br/>EnumerateKeys / GetValue"]
end
subgraph "Native View (64-bit)"
NATIVE["HKLM\Software\Microsoft\Office"]
NATIVE_OPEN["RegOpenKeyEx with<br/>KEY_WOW64_64KEY"]
end
subgraph "WOW64 View (32-bit)"
WOW["HKLM\Software\Wow6432Node\<br/>Microsoft\Office"]
WOW_OPEN["RegOpenKeyEx with<br/>KEY_WOW64_32KEY"]
end
HELPER --> CHECK
CHECK --> OP
OP -->|"Always"| NATIVE
NATIVE --> NATIVE_OPEN
OP -->|"If is64Bit"| WOW
WOW --> WOW_OPEN
The RegistryHelper class 334-616 implements WOW64 support through:
-
System Detection: The
_is64Bitfield 336 stores system architecture -
Path Transformation:
GetWow64Key(string subKey)583-593 transforms registry paths:-
Software\Microsoft\Office→Software\Wow6432Node\Microsoft\Office -
Software\Classes\X→Software\Classes\Wow6432Node\X
-
-
Dual Operations: Every registry operation executes twice on 64-bit systems:
- Once on the native 64-bit view
- Once on the WOW64 32-bit redirected view
Example from RegistryHelper.KeyExists() 343-347:
public bool KeyExists(RegistryHiveType hive, string subKey)
{
return KeyExistsInternal(GetHiveKey(hive), subKey, false) ||
(_is64Bit && KeyExistsInternal(GetHiveKey(hive), GetWow64Key(subKey), false));
}To prevent redundant operations, EnumerateKeys() and EnumerateValues() use HashSet<string> with StringComparer.OrdinalIgnoreCase 404-435 to merge results from both views.
Sources: OfficeScrubC2R-Native.cs:334-616, docs/memory-bank/guidelines.md:57-62
The system uses Task.Run() and Task.WaitAll() for parallel execution of independent operations, particularly in process termination.
Parallel Process Termination
sequenceDiagram
participant Caller
participant ProcessHelper
participant Task1
participant Task2
participant TaskN
participant Lock
Caller->>ProcessHelper: TerminateProcesses(processNames[], 5000ms)
ProcessHelper->>ProcessHelper: Enumerate matching processes
par Parallel Execution
ProcessHelper->>Task1: Task.Run(() => Kill winword.exe PID 1234)
ProcessHelper->>Task2: Task.Run(() => Kill excel.exe PID 5678)
ProcessHelper->>TaskN: Task.Run(() => Kill outlook.exe PID 9012)
end
Task1->>Task1: process.Kill()<br/>WaitForExit(5000)
Task2->>Task2: process.Kill()<br/>WaitForExit(5000)
TaskN->>TaskN: process.Kill()<br/>WaitForExit(5000)
Task1->>Lock: lock(terminatedPids)<br/>Add PID 1234
Task2->>Lock: lock(terminatedPids)<br/>Add PID 5678
TaskN->>Lock: lock(terminatedPids)<br/>Add PID 9012
ProcessHelper->>ProcessHelper: Task.WaitAll(tasks, timeout * 2)
ProcessHelper-->>Caller: Return List<int> terminatedPids
Located at OfficeScrubC2R-Native.cs:775-813, this method:
- Enumerate Processes: Get all processes matching the provided names
-
Create Tasks: Spawn a
Task.Run()for each process instance -
Parallel Kill: Each task independently calls
process.Kill()andprocess.WaitForExit(timeoutMs) -
Thread-Safe Collection: Use
lock (terminatedPids)to safely add PIDs from multiple threads 796-798 -
Wait for Completion:
Task.WaitAll(tasks.ToArray(), timeoutMs * 2)with double timeout to allow all operations to finish 812
- Performance: Terminate multiple processes simultaneously instead of sequentially
- Robustness: Individual task failures don't affect other terminations
- Timeout Control: Global timeout prevents indefinite hangs
Sources: OfficeScrubC2R-Native.cs:773-813, docs/memory-bank/guidelines.md:63-68, docs/memory-bank/tech.md:104-109
When files cannot be deleted due to locks (typically from running services or kernel mode drivers), the system schedules deletion for the next reboot using the Windows MoveFileEx API.
File Deletion Strategy with Reboot Fallback
graph TB
START["DeleteFile or<br/>DeleteDirectory"]
EXISTS{File/Dir<br/>Exists?}
ATTR["Remove ReadOnly<br/>Attribute"]
TYPE{Type?}
FILE_DEL["File.Delete()"]
DIR_STRATEGY{Large<br/>Directory?}
CMD["cmd.exe rd /s /q<br/>Fast bulk delete"]
NET["Directory.Delete()<br/>.NET fallback"]
SUCCESS{Deleted?}
SCHEDULE{scheduleOnFail<br/>parameter?}
REBOOT["MoveFileEx with<br/>MOVEFILE_DELAY_UNTIL_REBOOT"]
REGISTRY["Add to Registry:<br/>PendingFileRenameOperations"]
TRACK["Add to _pendingDeletes<br/>internal list"]
FAIL["Return false"]
SUCC["Return true"]
START --> EXISTS
EXISTS -->|No| SUCC
EXISTS -->|Yes| ATTR
ATTR --> TYPE
TYPE -->|File| FILE_DEL
TYPE -->|Directory| DIR_STRATEGY
DIR_STRATEGY -->|Yes| CMD
DIR_STRATEGY -->|No| NET
CMD --> SUCCESS
NET --> SUCCESS
FILE_DEL --> SUCCESS
SUCCESS -->|Yes| SUCC
SUCCESS -->|No| SCHEDULE
SCHEDULE -->|No| FAIL
SCHEDULE -->|Yes| REBOOT
REBOOT --> REGISTRY
REGISTRY --> TRACK
TRACK --> FAIL
Located at OfficeScrubC2R-Native.cs:626-651:
-
Check Existence: Return
trueif file doesn't exist (already deleted) -
Remove Protection: Clear
ReadOnlyattribute if present 634-638 -
Attempt Deletion: Call
File.Delete()640 -
Fallback: If deletion fails and
scheduleOnFailistrue, callScheduleDeleteOnReboot()645-647
Located at OfficeScrubC2R-Native.cs:689-697:
-
Call MoveFileEx:
NativeMethods.MoveFileEx(filePath, null, MOVEFILE_DELAY_UNTIL_REBOOT)693- First parameter: File to delete
- Second parameter:
null(indicates deletion, not move) - Third parameter:
MOVEFILE_DELAY_UNTIL_REBOOTflag (0x4)
-
Update Registry: This API automatically adds the file to
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations -
Track Internally: Add to
_pendingDeleteslist for reporting 694
For directories OfficeScrubC2R-Native.cs:699-726, the process is more complex:
- Schedule Files First: Enumerate all files recursively and schedule each
- Schedule Directories: Enumerate directories, sort by depth (deepest first), schedule in reverse order
- Schedule Root: Finally schedule the root directory itself
This ordering ensures proper cleanup on reboot, as Windows processes PendingFileRenameOperations in order and cannot delete non-empty directories.
For large directories, DeleteDirectory() first attempts cmd.exe /c rd /s /q 660-670 before falling back to Directory.Delete() 676. This provides approximately 15x performance improvement for bulk deletion.
Sources: OfficeScrubC2R-Native.cs:622-726, docs/memory-bank/guidelines.md:77-83, docs/memory-bank/tech.md:85-88
Windows Installer stores GUIDs in three different formats, requiring bidirectional transformation for metadata cleanup.
GUID Format Transformations
graph LR
subgraph "GUID Formats"
EXP["Expanded GUID<br/>38 chars<br/>{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"]
COMP["Compressed GUID<br/>32 chars<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"]
ENC["Encoded GUID<br/>20 chars Base85<br/>XXXXXXXXXXXXXXXXXXXX"]
end
subgraph "GuidHelper Methods"
GET_EXP["GetExpandedGuid<br/>Line 173"]
GET_COMP["GetCompressedGuid<br/>Line 216"]
GET_DEC["GetDecodedGuid<br/>Line 255"]
end
subgraph "Storage Locations"
REG_KEY["Registry Keys<br/>UpgradeCodes<br/>Products<br/>Components"]
REG_VAL["Registry Values<br/>Component GUIDs"]
PUB_COMP["PublishedComponents<br/>Multi-String Data"]
end
COMP -->|"Transform"| GET_EXP
GET_EXP --> EXP
EXP -->|"Reverse"| GET_COMP
GET_COMP --> COMP
ENC -->|"Decode"| GET_DEC
GET_DEC --> EXP
REG_KEY -->|"32-char names"| COMP
REG_VAL -->|"32-char values"| COMP
PUB_COMP -->|"20-char prefix"| ENC
| Format | Length | Example | Usage |
|---|---|---|---|
| Expanded | 38 chars | {90160000-008C-0000-0000-0000000FF1CE} |
Standard product codes, validation |
| Compressed | 32 chars | 00061900C80000000000000FF1CE |
Registry key names (UpgradeCodes, Products, Components) |
| Encoded | 20 chars | 9n0P>Hp4FoaA*8Kc5oA |
PublishedComponents multi-string data |
Located at OfficeScrubC2R-Native.cs:173-214, this method transforms a compressed GUID to expanded format:
- Validate Length: Must be exactly 32 characters
-
Reverse Segments: First three segments are byte-reversed:
- Characters 0-7 → Reversed into positions 1-8
- Characters 8-11 → Reversed into positions 10-13
- Characters 12-15 → Reversed into positions 15-18
-
Pair Swap: Last two segments are pair-swapped:
- Each pair of characters swaps positions
- Format: Add braces and dashes to create standard GUID format
Located at OfficeScrubC2R-Native.cs:255-320, this method decodes Base85-encoded GUIDs:
- Validate Length: Must be exactly 20 characters
- Decode Table: Use pre-defined lookup table for ASCII to Base85 value mapping 264-274
- Process in Groups: Decode 5 characters at a time into 32-bit values
-
Accumulate: Each character contributes
value * (85^position)to the total - Format: Rearrange decoded bytes into standard GUID format
The WindowsInstallerHelper class uses these transformations extensively:
-
CleanupUpgradeCodes() 989-1011: Enumerates 32-char registry keys, expands to GUIDs, passes to
shouldDeletecallback - CleanupProducts() 1013-1038: Same pattern for Product keys
- CleanupComponents() 1040-1066: Processes both component keys and value names
- CleanupPublishedComponents() 1069-1093: Decodes 20-char Base85 prefixes in multi-string data
The shouldDelete callback is typically OfficeScrubOrchestrator.IsInScope() 1361-1382, which validates GUIDs against Office product patterns.
Sources: OfficeScrubC2R-Native.cs:171-328, OfficeScrubC2R-Native.cs:980-1095
This section illustrates how a typical cleanup request flows through the system layers.
End-to-End Execution Flow
sequenceDiagram
participant User
participant PSM1 as OfficeScrubC2R.psm1
participant Native as OfficeScrubNative.dll
participant Orch as OfficeScrubOrchestrator
participant Reg as RegistryHelper
participant File as FileHelper
participant Proc as ProcessHelper
participant WI as WindowsInstallerHelper
User->>PSM1: Invoke-OfficeScrubC2R
PSM1->>PSM1: Import-OfficeScrubNative<br/>(Load DLL or compile)
PSM1->>Native: New-OfficeScrubOrchestrator
Native->>Orch: new OfficeScrubOrchestrator(is64Bit)
Orch->>Reg: new RegistryHelper(is64Bit)
Orch->>File: new FileHelper()
Orch->>Proc: new ProcessHelper()
Orch->>WI: new WindowsInstallerHelper(regHelper)
PSM1->>Orch: IsC2RPath(installPath)
Orch-->>PSM1: bool (validation result)
Note over PSM1: Stage 1: Uninstall
PSM1->>Proc: TerminateProcesses(OFFICE_PROCESSES, 5000)
Proc-->>PSM1: List<int> terminatedPids
Note over PSM1: Stage 2: Registry Cleanup
PSM1->>Reg: EnumerateKeys(HKLM, "Software\\Microsoft\\Office\\16.0")
Reg-->>PSM1: string[] subkeys
loop For each Office key
PSM1->>Orch: IsInScope(productGuid)
Orch-->>PSM1: bool shouldDelete
alt shouldDelete == true
PSM1->>Reg: DeleteKey(HKLM, keyPath, recursive)
Reg-->>PSM1: bool success
end
end
PSM1->>WI: CleanupProducts(IsInScope callback)
WI->>Reg: EnumerateKeys(HKLM, "...\\Products")
Reg-->>WI: string[] productKeys
loop For each product
WI->>WI: GuidHelper.GetExpandedGuid(compressedKey)
WI->>Orch: shouldDelete(expandedGuid)
Orch-->>WI: bool
WI->>Reg: DeleteKey(HKLM, productKeyPath)
end
Note over PSM1: Stage 3: File System Cleanup
PSM1->>File: DeleteDirectory("C:\\Program Files\\Microsoft Office", true, true)
File->>File: cmd.exe rd /s /q
alt Deletion successful
File-->>PSM1: true
else Locked files
File->>File: ScheduleDeleteOnReboot(path)
File->>File: MoveFileEx(path, null, MOVEFILE_DELAY_UNTIL_REBOOT)
File-->>PSM1: false (scheduled for reboot)
end
PSM1->>File: GetPendingDeletes()
File-->>PSM1: List<string> pendingFiles
PSM1-->>User: Exit code + log file location
- Initialization: Load native DLL, create orchestrator, instantiate helpers
-
Detection: Use
IsC2RPath()andIsInScope()to identify cleanup targets - Stage 1: License cleanup, process termination, shell cleanup
- Stage 2: Registry enumeration and deletion across multiple hives, WOW64 support, MSI metadata cleanup
- Stage 3: File and directory deletion with reboot scheduling fallback
- Reporting: Return exit code, pending deletes, and log location
-
Path Validation:
OfficeScrubOrchestrator.IsC2RPath()checks if paths contain C2R patterns fromOfficeConstants.C2R_PATTERNS36-45 -
Scope Validation:
OfficeScrubOrchestrator.IsInScope()validates Office product GUIDs: -
WOW64 Handling:
RegistryHelper._is64Bitdetermines if dual-path operations are needed -
Reboot Requirement: Presence of items in
FileHelper._pendingDeletesindicates reboot needed
Sources: OfficeScrubC2R.psm1:1-500, OfficeScrubC2R-Native.cs:1315-1383
The OfficeScrubC2R architecture achieves its performance goals through:
- Layered Design: PowerShell for orchestration, C# for performance-critical operations, Win32 APIs for low-level access
- Specialized Components: Eight focused helper classes, each handling a specific Windows subsystem
- Performance Patterns: Parallel processing, native API access, optimized algorithms (cmd.exe rd, StringBuilder, HashSet)
- Robustness Features: WOW64 dual-path handling, reboot scheduling for locked files, graceful degradation
- Systematic Cleanup: Three-stage pipeline ensuring complete Office removal
This architecture enables 10-50x performance improvements over the original VBScript while maintaining complete feature parity and adding modern capabilities like parallel execution and comprehensive error handling.
Sources: OfficeScrubC2R-Native.cs:1-2000, docs/memory-bank/product.md:1-72, docs/memory-bank/structure.md:1-159, docs/memory-bank/tech.md:1-157, docs/memory-bank/guidelines.md:1-179