Skip to content

ProcessHelper

Calvindd2f edited this page Jan 14, 2026 · 1 revision

ProcessHelper

Relevant source files

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

Purpose and Scope

The ProcessHelper class provides high-performance process management operations for terminating Office-related processes during uninstallation. It is part of the Native Performance Layer and handles parallel process termination with timeout control and proper resource disposal. This class is specifically designed to ensure all Office processes are stopped before file and registry cleanup operations begin.

For registry operations, see RegistryHelper. For file operations, see FileHelper. For Windows Installer MSI cleanup, see WindowsInstallerHelper.

Sources: OfficeScrubC2R-Native.cs:773-858


Class Structure

The ProcessHelper class is a simple, stateless helper that exposes three public methods for process management. Unlike other helpers, it does not maintain internal state and does not require initialization parameters.

classDiagram
    class ProcessHelper {
        +TerminateProcesses(string[] processNames, int timeoutMs) List~int~
        +GetProcessesUsingPath(string path) List~string~
        +IsProcessRunning(string processName) bool
    }
    
    class OfficeScrubOrchestrator {
        +ProcessHelper Processes
    }
    
    class Process {
        <<System.Diagnostics>>
        +Id : int
        +HasExited : bool
        +Kill() void
        +WaitForExit(int) bool
        +Dispose() void
    }
    
    class Task {
        <<System.Threading.Tasks>>
        +Run(Action) Task
        +WaitAll(Task[], int) bool
    }
    
    OfficeScrubOrchestrator --> ProcessHelper
    ProcessHelper ..> Process : uses
    ProcessHelper ..> Task : uses
Loading

Sources: OfficeScrubC2R-Native.cs:773-858, OfficeScrubC2R-Native.cs:1349-1370


Public Methods

TerminateProcesses()

The primary method for parallel process termination. Accepts an array of process names and terminates all matching processes concurrently.

Parameter Type Default Description
processNames string[] - Array of process names to terminate (with or without .exe extension)
timeoutMs int 5000 Maximum time in milliseconds to wait for each process to exit
Returns List<int> - List of PIDs that were successfully terminated

Implementation Details:

  • Uses Path.GetFileNameWithoutExtension() to normalize process names
  • Creates parallel tasks with Task.Run() for each process
  • Uses thread-safe lock on the returned list
  • Waits up to timeoutMs * 2 for all tasks to complete
  • Disposes Process objects in finally blocks to prevent resource leaks

Sources: OfficeScrubC2R-Native.cs:775-814

GetProcessesUsingPath()

Identifies all running processes whose main module executable is located under a specific path. Used to detect processes that would block file deletion operations.

Parameter Type Description
path string File system path to check
Returns List<string> List of process names using the specified path

Implementation Details:

  • Enumerates all system processes with Process.GetProcesses()
  • Checks process.MainModule.FileName against the path
  • Uses case-insensitive comparison (StringComparison.OrdinalIgnoreCase)
  • Silently ignores processes where MainModule access throws (typically system/protected processes)
  • Always disposes process objects

Sources: OfficeScrubC2R-Native.cs:816-842

IsProcessRunning()

Simple check for whether any instance of a process is currently running.

Parameter Type Description
processName string Process name to check (with or without .exe extension)
Returns bool true if at least one instance is running

Key Implementation Detail: This method demonstrates the proper disposal pattern. The implementation ensures all processes are disposed immediately after checking to prevent resource leaks.

Sources: OfficeScrubC2R-Native.cs:844-857


Parallel Execution Architecture

The TerminateProcesses() method achieves significant performance improvements through parallel execution. When terminating multiple processes, each termination operation runs concurrently.

sequenceDiagram
    participant Caller
    participant ProcessHelper
    participant TaskScheduler as "Task Scheduler"
    participant P1 as "Process 1"
    participant P2 as "Process 2"
    participant P3 as "Process 3"
    
    Caller->>ProcessHelper: TerminateProcesses(["winword.exe", "excel.exe", "outlook.exe"], 5000)
    
    ProcessHelper->>ProcessHelper: Get all instances
    Note over ProcessHelper: Found: winword.exe (PID 1234)<br/>excel.exe (PID 5678)<br/>outlook.exe (PID 9012)
    
    par Parallel Termination
        ProcessHelper->>TaskScheduler: Task.Run(() => Kill PID 1234)
        TaskScheduler->>P1: Kill()
        P1->>P1: WaitForExit(5000ms)
        P1->>TaskScheduler: Success
        TaskScheduler->>ProcessHelper: lock(terminatedPids)<br/>Add 1234
    and
        ProcessHelper->>TaskScheduler: Task.Run(() => Kill PID 5678)
        TaskScheduler->>P2: Kill()
        P2->>P2: WaitForExit(5000ms)
        P2->>TaskScheduler: Success
        TaskScheduler->>ProcessHelper: lock(terminatedPids)<br/>Add 5678
    and
        ProcessHelper->>TaskScheduler: Task.Run(() => Kill PID 9012)
        TaskScheduler->>P3: Kill()
        P3->>P3: WaitForExit(5000ms)
        P3->>TaskScheduler: Success
        TaskScheduler->>ProcessHelper: lock(terminatedPids)<br/>Add 9012
    end
    
    ProcessHelper->>ProcessHelper: Task.WaitAll(tasks, 10000ms)
    ProcessHelper->>Caller: Return [1234, 5678, 9012]
Loading

Performance Impact:

  • Sequential: 3 processes × 5000ms timeout = 15,000ms maximum
  • Parallel: 1 × 5000ms timeout = 5,000ms maximum
  • Speedup: ~3x for 3 processes, scales linearly with process count

Sources: OfficeScrubC2R-Native.cs:775-814


Thread Safety and Resource Management

Thread-Safe Collection Updates

The TerminateProcesses() method uses proper locking when multiple threads update the shared terminatedPids list:

flowchart TD
    Start["Task.Run(() => {...})"]
    CheckExited{"process.HasExited?"}
    Kill["process.Kill()"]
    Wait["process.WaitForExit(timeoutMs)"]
    WaitSuccess{"Exited?"}
    Lock["lock(terminatedPids)"]
    Add["terminatedPids.Add(pid)"]
    Dispose["process.Dispose()"]
    End["Task completes"]
    
    Start --> CheckExited
    CheckExited -->|false| Kill
    CheckExited -->|true| Dispose
    Kill --> Wait
    Wait --> WaitSuccess
    WaitSuccess -->|true| Lock
    WaitSuccess -->|false| Dispose
    Lock --> Add
    Add --> Dispose
    Dispose --> End
Loading

Critical Sections:

  • The lock (terminatedPids) statement at OfficeScrubC2R-Native.cs:796-799 ensures only one thread at a time can add to the list
  • Without this lock, concurrent updates could corrupt the list's internal state
  • The lock scope is minimal (only 3 lines) to avoid blocking other threads unnecessarily

Resource Disposal Pattern

All three methods follow a consistent resource disposal pattern:

The finally block pattern ensures disposal even when exceptions occur:

try
{
    // Process operations
}
catch { }
finally
{
    process.Dispose();  // Always executes
}

For methods that enumerate processes, disposal happens immediately after use:

var processes = Process.GetProcessesByName(...);
var hasRunning = processes.Length > 0;
foreach (var p in processes) p.Dispose();
return hasRunning;

Sources: OfficeScrubC2R-Native.cs:804-807, OfficeScrubC2R-Native.cs:832-835, OfficeScrubC2R-Native.cs:849-851


Integration with Cleanup Pipeline

The ProcessHelper is exposed through the OfficeScrubOrchestrator and used at multiple stages of the three-stage cleanup pipeline.

graph TB
    subgraph "OfficeScrubOrchestrator"
        Processes["ProcessHelper Processes"]
    end
    
    subgraph "Stage 0: Detection"
        Detect["Get-InstalledOfficeProducts"]
    end
    
    subgraph "Stage 1: Uninstall"
        PreUninstall["Stop-OfficeProcesses<br/>(Before ODT)"]
        ODT["Execute Office Deployment Tool"]
        PostUninstall["Stop-OfficeProcesses<br/>(After ODT)"]
        MSI["MSI Uninstall"]
    end
    
    subgraph "Stage 2: Cleanup"
        CheckLocked["Check for locked files<br/>GetProcessesUsingPath()"]
        Registry["Registry cleanup"]
        Files["File cleanup"]
    end
    
    OfficeConstants["OfficeConstants.OFFICE_PROCESSES<br/>25 process names"]
    
    PreUninstall --> Processes
    PostUninstall --> Processes
    CheckLocked --> Processes
    
    OfficeConstants -.process list.-> PreUninstall
    OfficeConstants -.process list.-> PostUninstall
    
    Processes --> TerminateProcesses["TerminateProcesses()"]
    Processes --> GetProcessesUsingPath["GetProcessesUsingPath()"]
    Processes --> IsProcessRunning["IsProcessRunning()"]
Loading

Key Integration Points:

  1. Pre-ODT Termination: Before executing the Office Deployment Tool, all Office processes must be stopped (Stage 1 cleanup)

  2. Post-ODT Termination: After ODT completes, verify no Office processes restarted

  3. Pre-MSI Termination: Before MSI uninstallation, terminate processes again to ensure clean MSI removal

  4. Locked File Detection: Before file cleanup, GetProcessesUsingPath() identifies processes that would prevent deletion

  5. Process List: Uses the 25 Office process names from OfficeConstants.OFFICE_PROCESSES OfficeScrubC2R-Native.cs:47-56

Sources: OfficeScrubC2R-Native.cs:1349-1370, OfficeScrubC2R-Native.cs:47-56


Office Process Names

The helper terminates processes from the predefined list in OfficeConstants.OFFICE_PROCESSES:

Category Processes
Core Office Apps winword.exe, excel.exe, powerpnt.exe, outlook.exe, onenote.exe, mspub.exe, msaccess.exe
Communication communicator.exe, lync.exe, skype.exe, teams.exe
Click-to-Run officeclicktorun.exe, officeondemand.exe, OfficeC2RClient.exe, integrator.exe, integratedoffice.exe
Supporting firstrun.exe, msosync.exe, OneNoteM.exe, roamingoffice.exe, perfboost.exe
System appvshnotify.exe, iexplore.exe, mavinject32.exe, werfault.exe

Note: Process names are normalized by Path.GetFileNameWithoutExtension(), so the .exe extension is optional when calling methods.

Sources: OfficeScrubC2R-Native.cs:47-56


Error Handling Strategy

The ProcessHelper follows the "silent failure" pattern documented across the native layer. All methods use empty catch blocks and return partial results rather than throwing exceptions.

Termination Failure Behavior

flowchart LR
    Input["Input: 5 processes"]
    P1["Process 1<br/>✓ Terminated"]
    P2["Process 2<br/>✗ Access Denied"]
    P3["Process 3<br/>✓ Terminated"]
    P4["Process 4<br/>✗ Already Exited"]
    P5["Process 5<br/>✓ Terminated"]
    Output["Output: [1, 3, 5]<br/>Partial success"]
    
    Input --> P1
    Input --> P2
    Input --> P3
    Input --> P4
    Input --> P5
    
    P1 --> Output
    P2 -.-x Output
    P3 --> Output
    P4 -.-x Output
    P5 --> Output
Loading

Rationale:

  • Office uninstallation should proceed even if some processes cannot be terminated
  • Protected system processes may deny access (e.g., werfault.exe running under SYSTEM)
  • Processes may exit on their own between enumeration and termination
  • Returning partial results allows the caller to decide if the success rate is acceptable

Common Failure Scenarios:

  • Access Denied: Process running with higher privileges
  • Process has exited: Process terminated between GetProcessesByName() and Kill()
  • MainModule access denied: System or protected process in GetProcessesUsingPath()

Sources: OfficeScrubC2R-Native.cs:789-803, OfficeScrubC2R-Native.cs:821-833


Performance Characteristics

Execution Timing

Operation Sequential Parallel Speedup
Terminate 3 processes ~15,000ms ~5,000ms 3x
Terminate 10 processes ~50,000ms ~5,000ms 10x
Terminate 25 processes ~125,000ms ~5,000ms 25x

Assumptions:

  • Each process takes up to 5000ms timeout to exit
  • Parallel execution limited only by CPU thread pool (typically sufficient for 25 processes)
  • Actual time typically much less as most processes exit quickly

Memory Characteristics

  • Process objects: ~4KB per process instance
  • Task overhead: ~1KB per task
  • Collection overhead: Minimal (List for PIDs)
  • Peak memory: ~125KB for terminating all 25 Office processes

Implementation Note: The proper disposal pattern prevents memory leaks from accumulating Process objects. Each process object is explicitly disposed in finally blocks or immediately after use.

Sources: OfficeScrubC2R-Native.cs:775-814


Usage Examples

Basic Termination

var orchestrator = new OfficeScrubOrchestrator(Environment.Is64BitOperatingSystem);
var processHelper = orchestrator.Processes;

// Terminate Office applications
var terminated = processHelper.TerminateProcesses(
    OfficeConstants.OFFICE_PROCESSES, 
    timeoutMs: 5000
);

Console.WriteLine($"Terminated {terminated.Count} processes");
foreach (var pid in terminated)
{
    Console.WriteLine($"  PID: {pid}");
}

Check Before Cleanup

// Before deleting Office installation directory
var path = @"C:\Program Files\Microsoft Office";
var processes = processHelper.GetProcessesUsingPath(path);

if (processes.Count > 0)
{
    Console.WriteLine($"Warning: {processes.Count} processes still using path:");
    foreach (var proc in processes)
    {
        Console.WriteLine($"  - {proc}");
    }
}

Verify Process State

// Check if specific Office app is running
if (processHelper.IsProcessRunning("winword.exe"))
{
    Console.WriteLine("Word is still running");
}

Sources: OfficeScrubC2R-Native.cs:773-858


Comparison to PowerShell Approach

The native ProcessHelper provides significant advantages over PowerShell's Stop-Process cmdlet:

Feature PowerShell Stop-Process ProcessHelper
Execution Model Sequential Parallel
Multiple Processes Requires loop Single method call
Timeout Control Manual with -Wait Built-in per process
Resource Disposal Automatic (GC) Explicit (finally block)
Return Value None List of terminated PIDs
Error Handling Throws exceptions Silent, returns partial results
Performance ~125s for 25 processes ~5s for 25 processes

Key Advantage: The parallel execution model is the primary performance benefit. When terminating 25 Office processes with 5-second timeouts, PowerShell would execute sequentially (worst case: 125 seconds), while ProcessHelper executes in parallel (worst case: 5 seconds).

Sources: OfficeScrubC2R-Native.cs:775-814


Related Components

Sources: OfficeScrubC2R-Native.cs:1349-1370

Clone this wiki locally