Skip to content

Architecture

Calvindd2f edited this page Jan 14, 2026 · 1 revision

Architecture

Relevant source files

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

Purpose and Scope

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.


Overall System Architecture

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
Loading

Sources: OfficeScrubC2R-Native.cs:1-2000, OfficeScrubC2R.psm1:1-500, docs/memory-bank/structure.md:60-106


Hybrid Architecture Approach

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.

Design Rationale

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+

Load Strategy and Fallback

The PowerShell module implements a smart loading strategy with automatic fallback:

  1. Primary: Load pre-compiled OfficeScrubNative.dll
  2. Fallback: Compile OfficeScrubC2R-Native.cs on-demand if DLL is missing or incompatible
  3. 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


Component Layers

PowerShell Layer

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
Loading

OfficeScrubC2R.psd1 (Module Manifest)

Defines module metadata, dependencies, and exports. Key properties:

  • ModuleVersion: Semantic version tracking
  • GUID: Unique module identifier
  • NestedModules: References OfficeScrubC2R-Utilities.psm1
  • RequiredAssemblies: Specifies OfficeScrubNative.dll
  • FunctionsToExport: Public API surface

OfficeScrubC2R.psm1 (Main Module)

Contains the core PowerShell functions:

  • Import-OfficeScrubNative: Loads the native DLL with fallback compilation
  • New-OfficeScrubOrchestrator: Creates an OfficeScrubOrchestrator instance
  • 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

OfficeScrubC2R-Utilities.psm1 (Nested Module)

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


Native C# Layer

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
Loading

OfficeScrubOrchestrator

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 is64Bit parameter 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

Helper Classes

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

Utility Classes

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


Win32 API Integration Layer

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
Loading

Registry APIs (advapi32.dll)

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

File System APIs (kernel32.dll)

Used by FileHelper for file operations:

  • MoveFileEx: Schedules file deletion on reboot with MOVEFILE_DELAY_UNTIL_REBOOT flag
  • SetFileAttributes: Modifies file attributes (e.g., removing read-only)
  • RemoveDirectory: Directly removes directories

Shell APIs (shell32.dll)

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

WMI Integration

Several helper classes use System.Management for WMI operations:

  • LicenseHelper: Queries SoftwareLicensingProduct / OfficeSoftwareProtectionProduct 1186-1218
  • ServiceHelper: Queries Win32_Service for service management 1251-1280

Sources: OfficeScrubC2R-Native.cs:62-165, docs/memory-bank/tech.md:82-95, docs/memory-bank/guidelines.md:52-56


Architectural Patterns

Three-Stage Cleanup Pipeline

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"]
Loading

Stage 1: Uninstall and Preparation

Objective: Prepare the system for cleanup by removing licenses, terminating processes, and attempting graceful uninstall.

  1. License Cleanup: LicenseHelper.CleanOSPP() removes Software Protection Platform keys 1184-1219
  2. Process Termination: ProcessHelper.TerminateProcesses() kills Office processes defined in OfficeConstants.OFFICE_PROCESSES 47-55
  3. Task Removal: Scheduled tasks are deleted
  4. Shell Cleanup: Shortcuts are unpinned using ShellHelper.UnpinFromTaskbar() and UnpinFromStartMenu() 866-947
  5. ODT/MSI Uninstall: Attempt standard uninstallation if applicable

Stage 2: Registry Cleanup

Objective: Remove all registry artifacts across user and machine hives, including Windows Installer metadata.

  1. User Registry: Delete keys under HKEY_CURRENT_USER\Software\Microsoft\Office
  2. Machine Registry: Delete keys under HKEY_LOCAL_MACHINE\Software\Microsoft\Office
  3. Windows Installer:
    • WindowsInstallerHelper.CleanupUpgradeCodes() 989-1011
    • WindowsInstallerHelper.CleanupProducts() 1013-1038
    • WindowsInstallerHelper.CleanupComponents() 1040-1066
  4. COM Type Libraries: TypeLibHelper.CleanupKnownTypeLibs() 1125-1174
  5. Shell Integration: Remove file associations and context menu handlers

Stage 3: File System Cleanup

Objective: Delete Office files, folders, and services, with fallback to reboot scheduling.

  1. Service Deletion: ServiceHelper.DeleteService() removes ClickToRun services 1247-1308
  2. File Deletion: FileHelper.DeleteFile() removes individual files 626-651
  3. Directory Deletion: FileHelper.DeleteDirectory() removes folders using cmd.exe rd /s /q for performance 653-686
  4. Reboot Scheduling: Locked files are scheduled for deletion via FileHelper.ScheduleDeleteOnReboot() using MoveFileEx API 689-697

Sources: OfficeScrubC2R-Native.cs:1315-2000, docs/memory-bank/product.md:14-41


WOW64 Registry Handling

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
Loading

Implementation Details

The RegistryHelper class 334-616 implements WOW64 support through:

  1. System Detection: The _is64Bit field 336 stores system architecture
  2. Path Transformation: GetWow64Key(string subKey) 583-593 transforms registry paths:
    • Software\Microsoft\Office → Software\Wow6432Node\Microsoft\Office
    • Software\Classes\X → Software\Classes\Wow6432Node\X
  3. 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));
}

Deduplication

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


Parallel Processing Pattern

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
Loading

ProcessHelper.TerminateProcesses() Implementation

Located at OfficeScrubC2R-Native.cs:775-813, this method:

  1. Enumerate Processes: Get all processes matching the provided names
  2. Create Tasks: Spawn a Task.Run() for each process instance
  3. Parallel Kill: Each task independently calls process.Kill() and process.WaitForExit(timeoutMs)
  4. Thread-Safe Collection: Use lock (terminatedPids) to safely add PIDs from multiple threads 796-798
  5. Wait for Completion: Task.WaitAll(tasks.ToArray(), timeoutMs * 2) with double timeout to allow all operations to finish 812

Benefits

  • 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


Reboot Scheduling Pattern

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
Loading

FileHelper.DeleteFile() Implementation

Located at OfficeScrubC2R-Native.cs:626-651:

  1. Check Existence: Return true if file doesn't exist (already deleted)
  2. Remove Protection: Clear ReadOnly attribute if present 634-638
  3. Attempt Deletion: Call File.Delete() 640
  4. Fallback: If deletion fails and scheduleOnFail is true, call ScheduleDeleteOnReboot() 645-647

FileHelper.ScheduleDeleteOnReboot() Implementation

Located at OfficeScrubC2R-Native.cs:689-697:

  1. 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_REBOOT flag (0x4)
  2. Update Registry: This API automatically adds the file to HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations
  3. Track Internally: Add to _pendingDeletes list for reporting 694

Directory Reboot Scheduling

For directories OfficeScrubC2R-Native.cs:699-726, the process is more complex:

  1. Schedule Files First: Enumerate all files recursively and schedule each
  2. Schedule Directories: Enumerate directories, sort by depth (deepest first), schedule in reverse order
  3. 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.

cmd.exe rd Optimization

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


GUID Transformation System

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
Loading

Format Details

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

GuidHelper.GetExpandedGuid() Implementation

Located at OfficeScrubC2R-Native.cs:173-214, this method transforms a compressed GUID to expanded format:

  1. Validate Length: Must be exactly 32 characters
  2. 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
  3. Pair Swap: Last two segments are pair-swapped:
    • Each pair of characters swaps positions
  4. Format: Add braces and dashes to create standard GUID format

GuidHelper.GetDecodedGuid() Implementation

Located at OfficeScrubC2R-Native.cs:255-320, this method decodes Base85-encoded GUIDs:

  1. Validate Length: Must be exactly 20 characters
  2. Decode Table: Use pre-defined lookup table for ASCII to Base85 value mapping 264-274
  3. Process in Groups: Decode 5 characters at a time into 32-bit values
  4. Accumulate: Each character contributes value * (85^position) to the total
  5. Format: Rearrange decoded bytes into standard GUID format

Usage in WindowsInstallerHelper

The WindowsInstallerHelper class uses these transformations extensively:

  • CleanupUpgradeCodes() 989-1011: Enumerates 32-char registry keys, expands to GUIDs, passes to shouldDelete callback
  • 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


Data Flow and Execution

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
Loading

Execution Phases

  1. Initialization: Load native DLL, create orchestrator, instantiate helpers
  2. Detection: Use IsC2RPath() and IsInScope() to identify cleanup targets
  3. Stage 1: License cleanup, process termination, shell cleanup
  4. Stage 2: Registry enumeration and deletion across multiple hives, WOW64 support, MSI metadata cleanup
  5. Stage 3: File and directory deletion with reboot scheduling fallback
  6. Reporting: Return exit code, pending deletes, and log location

Key Decision Points

  • Path Validation: OfficeScrubOrchestrator.IsC2RPath() checks if paths contain C2R patterns from OfficeConstants.C2R_PATTERNS 36-45
  • Scope Validation: OfficeScrubOrchestrator.IsInScope() validates Office product GUIDs:
    • Must end with {0000000FF1CE} (Office ID) 1372
    • Version must be > 14 (Office 2013+) 1376-1377
    • SKU must be in valid set: 007E, 008F, 008C, 24E1, 237A, 00DD 1318-1321
  • WOW64 Handling: RegistryHelper._is64Bit determines if dual-path operations are needed
  • Reboot Requirement: Presence of items in FileHelper._pendingDeletes indicates reboot needed

Sources: OfficeScrubC2R.psm1:1-500, OfficeScrubC2R-Native.cs:1315-1383


Summary

The OfficeScrubC2R architecture achieves its performance goals through:

  1. Layered Design: PowerShell for orchestration, C# for performance-critical operations, Win32 APIs for low-level access
  2. Specialized Components: Eight focused helper classes, each handling a specific Windows subsystem
  3. Performance Patterns: Parallel processing, native API access, optimized algorithms (cmd.exe rd, StringBuilder, HashSet)
  4. Robustness Features: WOW64 dual-path handling, reboot scheduling for locked files, graceful degradation
  5. 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

Clone this wiki locally