Repository navigation
ProcessHelper
Relevant source files
The following files were used as context for generating this wiki page:
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
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
Sources: OfficeScrubC2R-Native.cs:773-858, OfficeScrubC2R-Native.cs:1349-1370
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
lockon the returned list - Waits up to
timeoutMs * 2for all tasks to complete - Disposes
Processobjects in finally blocks to prevent resource leaks
Sources: OfficeScrubC2R-Native.cs:775-814
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.FileNameagainst the path - Uses case-insensitive comparison (
StringComparison.OrdinalIgnoreCase) - Silently ignores processes where
MainModuleaccess throws (typically system/protected processes) - Always disposes process objects
Sources: OfficeScrubC2R-Native.cs:816-842
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
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]
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
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
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
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
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()"]
Key Integration Points:
-
Pre-ODT Termination: Before executing the Office Deployment Tool, all Office processes must be stopped (Stage 1 cleanup)
-
Post-ODT Termination: After ODT completes, verify no Office processes restarted
-
Pre-MSI Termination: Before MSI uninstallation, terminate processes again to ensure clean MSI removal
-
Locked File Detection: Before file cleanup,
GetProcessesUsingPath()identifies processes that would prevent deletion -
Process List: Uses the 25 Office process names from
OfficeConstants.OFFICE_PROCESSESOfficeScrubC2R-Native.cs:47-56
Sources: OfficeScrubC2R-Native.cs:1349-1370, OfficeScrubC2R-Native.cs:47-56
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
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.
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
Rationale:
- Office uninstallation should proceed even if some processes cannot be terminated
- Protected system processes may deny access (e.g.,
werfault.exerunning 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 betweenGetProcessesByName()andKill() -
MainModule access denied: System or protected process inGetProcessesUsingPath()
Sources: OfficeScrubC2R-Native.cs:789-803, OfficeScrubC2R-Native.cs:821-833
| 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
- 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
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}");
}// 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}");
}
}// Check if specific Office app is running
if (processHelper.IsProcessRunning("winword.exe"))
{
Console.WriteLine("Word is still running");
}Sources: OfficeScrubC2R-Native.cs:773-858
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
- RegistryHelper: Handles registry cleanup operations
- FileHelper: Manages file and directory deletion with reboot scheduling
- WindowsInstallerHelper: Cleans MSI metadata
- OfficeScrubOrchestrator: Provides unified access to all helpers
- Three-Stage Execution Pipeline: Shows where process termination occurs in the workflow
Sources: OfficeScrubC2R-Native.cs:1349-1370