Repository navigation
Performance Architecture
Relevant source files
The following files were used as context for generating this wiki page:
This page explains how OfficeScrubC2R achieves 10-50x performance improvements over the original VBScript implementation through native C# code, parallel processing, memory optimizations, and direct Win32 API integration.
For information about the overall system design and component relationships, see High-Level Design. For details on the three-stage execution pipeline, see Three-Stage Execution Pipeline. For implementation details of specific helper classes, see Helper Classes.
The original Microsoft OffScrubC2R.vbs v2.19 suffered from significant performance bottlenecks due to its interpreted VBScript nature, single-threaded execution model, and COM automation overhead. The PowerShell/C# port addresses these limitations through architectural improvements.
| Operation | Time | Bottleneck |
|---|---|---|
| Registry enumeration | ~10 seconds | COM IWbemObjectSet iteration, single-threaded |
| File deletion | ~15 seconds | Sequential file I/O, no parallel execution |
| Overall execution | 5-10 minutes | Interpreted code, COM overhead |
| Operation | Time | Improvement | Implementation |
|---|---|---|---|
| Registry enumeration | ~0.3 seconds | 30x faster | Direct Win32 API, WOW64 support |
| File deletion | ~1 second | 15x faster | Parallel processing, cmd.exe optimization |
| Overall execution | 30-60 seconds | 10-50x faster | Compiled C#, native APIs |
Sources: docs/CHANGELOG.md:126-130, README.md:134-142, docs/OPTIMIZATION_ANALYSIS.md:250-260
The performance improvement is achieved through a hybrid architecture that delegates performance-critical operations to compiled C# code while maintaining PowerShell for orchestration and user interaction.
graph TB
subgraph "VBScript Implementation Baseline"
VBSEngine["VBScript Interpreter<br/>cscript.exe/wscript.exe"]
VBSCode["OffScrubC2R.vbs<br/>3,803 lines interpreted"]
VBSCOM["COM Automation<br/>WScript.Shell<br/>Scripting.FileSystemObject<br/>WMI"]
VBSReg["Registry via WMI<br/>Single-threaded<br/>StdRegProv provider"]
VBSFile["File I/O via FSO<br/>Sequential deletion<br/>DeleteFile/DeleteFolder"]
VBSEngine --> VBSCode
VBSCode --> VBSCOM
VBSCOM --> VBSReg
VBSCOM --> VBSFile
end
subgraph "PowerShell/C# Implementation"
PSEngine["PowerShell Engine<br/>pwsh.exe or powershell.exe"]
PSModule["OfficeScrubC2R.psm1<br/>Orchestration layer"]
PSMain["OfficeScrubC2R.ps1<br/>Pipeline logic"]
NativeDLL["OfficeScrubNative.dll<br/>Compiled .NET 4.0<br/>10 helper classes"]
RegHelper["RegistryHelper<br/>Direct Win32 RegOpenKeyEx<br/>WOW64 dual-view"]
FileHelper["FileHelper<br/>Parallel Task.Run<br/>cmd.exe rd optimization"]
PSEngine --> PSModule
PSModule --> PSMain
PSMain --> NativeDLL
NativeDLL --> RegHelper
NativeDLL --> FileHelper
end
VBSReg -.replaced by.-> RegHelper
VBSFile -.replaced by.-> FileHelper
Architecture Comparison
| Layer | VBScript | PowerShell/C# | Benefit |
|---|---|---|---|
| Execution | Interpreted line-by-line | Compiled IL, JIT to native | 10x base performance |
| Registry | WMI StdRegProv (COM) | advapi32.dll P/Invoke | 30x faster, direct API |
| File I/O | FSO sequential | Parallel + cmd.exe | 15x faster, multi-threaded |
| Type Safety | Variant (late binding) | Strong typing | Compile-time checks |
| Error Handling | On Error Resume Next | try-catch blocks | Proper exception handling |
Sources: OfficeScrubC2R-Native.cs:1-16, docs/OPTIMIZATION_ANALYSIS.md:1-41
The RegistryHelper class achieves 30x performance improvement through three optimization strategies: direct Win32 API calls via P/Invoke, WOW64 dual-view enumeration, and HashSet-based deduplication.
The VBScript implementation used WMI's StdRegProv provider with COM automation overhead. The C# implementation uses P/Invoke to call Win32 registry functions directly.
graph LR
subgraph "VBScript Registry Access"
VBS["VBScript Code"]
WMI["WMI Provider<br/>StdRegProv"]
COM["COM Interop<br/>IWbemServices"]
RegAPI1["advapi32.dll<br/>RegOpenKeyEx"]
VBS --> WMI
WMI --> COM
COM --> RegAPI1
end
subgraph "C# Registry Access"
CSharp["RegistryHelper.EnumerateKeys"]
PInvoke["P/Invoke Declaration<br/>NativeMethods.RegOpenKeyEx"]
RegAPI2["advapi32.dll<br/>RegOpenKeyEx"]
CSharp --> PInvoke
PInvoke --> RegAPI2
end
VBS -.4 layers of indirection.-> RegAPI1
CSharp -.direct call.-> RegAPI2
P/Invoke Declaration
The registry operations use direct P/Invoke to advapi32.dll functions:
OfficeScrubC2R-Native.cs:70-86 defines RegOpenKeyEx and RegDeleteKeyEx with proper marshaling:
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int RegOpenKeyEx(
UIntPtr hKey,
string lpSubKey,
uint ulOptions,
int samDesired,
out UIntPtr phkResult);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int RegDeleteKeyEx(
UIntPtr hKey,
string lpSubKey,
int samDesired,
uint Reserved);On 64-bit systems, the registry has separate views for 32-bit and 64-bit applications. The RegistryHelper enumerates both views to find all Office installations.
graph TB
subgraph "64-bit Registry Views"
Native64["Native 64-bit View<br/>HKLM\Software"]
WOW64["WOW64 32-bit View<br/>HKLM\Software\Wow6432Node"]
end
EnumerateKeys["RegistryHelper.EnumerateKeys"]
GetWow64Key["GetWow64Key method<br/>Injects Wow6432Node path"]
HashSet["HashSet<string><br/>StringComparer.OrdinalIgnoreCase<br/>Deduplicates results"]
EnumerateKeys --> Native64
EnumerateKeys --> GetWow64Key
GetWow64Key --> WOW64
Native64 --> HashSet
WOW64 --> HashSet
HashSet --> Result["Unified result array"]
OfficeScrubC2R-Native.cs:402-436 implements dual-view enumeration with deduplication:
public string[] EnumerateKeys(RegistryHiveType hive, string subKey)
{
var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
using (var key = OpenKey(GetHiveKey(hive), subKey, false))
{
if (key != null)
{
foreach (var name in key.GetSubKeyNames())
keys.Add(name);
}
}
}
catch { }
if (_is64Bit)
{
try
{
using (var key = OpenKey(GetHiveKey(hive), GetWow64Key(subKey), false))
{
if (key != null)
{
foreach (var name in key.GetSubKeyNames())
keys.Add(name);
}
}
}
catch { }
}
return keys.ToArray();
}The GetWow64Key method at OfficeScrubC2R-Native.cs:583-593 transforms registry paths to inject the WOW64 node.
| Operation | VBScript (WMI) | C# (Win32) | Speedup |
|---|---|---|---|
| Open registry key | ~50ms (COM) | ~2ms (P/Invoke) | 25x |
| Enumerate 100 subkeys | ~500ms | ~15ms | 33x |
| Delete key tree | ~200ms | ~8ms | 25x |
| Aggregate | ~750ms | ~25ms | 30x |
Sources: OfficeScrubC2R-Native.cs:333-617, docs/OPTIMIZATION_ANALYSIS.md:277-285
The FileHelper class achieves 15x performance improvement through parallel execution and cmd.exe optimization for directory trees.
Unlike VBScript's sequential file deletion, the C# implementation uses Task.Run for concurrent execution when processing multiple file paths.
graph TB
subgraph "VBScript Sequential Deletion"
VBSLoop["For Each file in folder"]
VBSDelete1["FSO.DeleteFile file1"]
VBSDelete2["FSO.DeleteFile file2"]
VBSDelete3["FSO.DeleteFile file3"]
VBSDelete4["FSO.DeleteFile file4"]
VBSLoop --> VBSDelete1
VBSDelete1 --> VBSDelete2
VBSDelete2 --> VBSDelete3
VBSDelete3 --> VBSDelete4
end
subgraph "C# Parallel Deletion"
TaskList["List<Task> tasks"]
Task1["Task.Run(() =><br/>File.Delete file1)"]
Task2["Task.Run(() =><br/>File.Delete file2)"]
Task3["Task.Run(() =><br/>File.Delete file3)"]
Task4["Task.Run(() =><br/>File.Delete file4)"]
WaitAll["Task.WaitAll(tasks)"]
TaskList --> Task1
TaskList --> Task2
TaskList --> Task3
TaskList --> Task4
Task1 --> WaitAll
Task2 --> WaitAll
Task3 --> WaitAll
Task4 --> WaitAll
end
For large directory trees, the FileHelper.DeleteDirectory method uses cmd.exe /c rd /s /q which is significantly faster than recursive .NET Directory.Delete calls due to kernel-level optimizations.
OfficeScrubC2R-Native.cs:653-687 implements the hybrid deletion strategy:
public bool DeleteDirectory(string directoryPath, bool recursive = true, bool scheduleOnFail = true)
{
if (!Directory.Exists(directoryPath))
return true;
try
{
// Try using cmd.exe rd first for performance
using (var process = Process.Start(new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c rd /s /q \"" + directoryPath + "\"",
UseShellExecute = false,
CreateNoWindow = true
}))
{
process.WaitForExit(30000);
}
if (!Directory.Exists(directoryPath))
return true;
// Fallback to .NET method
Directory.Delete(directoryPath, recursive);
return true;
}
catch
{
if (scheduleOnFail)
{
ScheduleDirectoryDeleteOnReboot(directoryPath);
}
return false;
}
}Why cmd.exe is Faster
| Method | Description | Performance |
|---|---|---|
Directory.Delete(path, true) |
Recursive C# calls, one file at a time | Baseline |
cmd.exe /c rd /s /q |
Windows kernel RemoveDirectoryTransacted | 3-5x faster |
The kernel-level optimization in rd /s /q uses transaction-based directory removal which batches I/O operations.
The ProcessHelper.TerminateProcesses method uses parallel task execution to terminate multiple Office processes simultaneously.
graph TB
ProcessNames["string[] processNames<br/>25 Office process names"]
GetProcs["Process.GetProcessesByName<br/>for each name"]
subgraph "Parallel Termination"
Task1["Task.Run(() =><br/>Kill winword.exe PID 1234)"]
Task2["Task.Run(() =><br/>Kill excel.exe PID 5678)"]
Task3["Task.Run(() =><br/>Kill outlook.exe PID 9012)"]
end
WaitAll["Task.WaitAll(tasks, timeout)"]
Results["List<int> terminatedPids"]
ProcessNames --> GetProcs
GetProcs --> Task1
GetProcs --> Task2
GetProcs --> Task3
Task1 --> WaitAll
Task2 --> WaitAll
Task3 --> WaitAll
WaitAll --> Results
OfficeScrubC2R-Native.cs:774-814 implements parallel process termination with proper disposal:
public List<int> TerminateProcesses(string[] processNames, int timeoutMs = 5000)
{
var terminatedPids = new List<int>();
var tasks = new List<Task>();
foreach (var processName in processNames)
{
var processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processName));
foreach (var process in processes)
{
var pid = process.Id;
tasks.Add(Task.Run(() =>
{
try
{
if (!process.HasExited)
{
process.Kill();
if (process.WaitForExit(timeoutMs))
{
lock (terminatedPids)
{
terminatedPids.Add(pid);
}
}
}
}
catch { }
finally
{
process.Dispose();
}
}));
}
}
Task.WaitAll(tasks.ToArray(), timeoutMs * 2);
return terminatedPids;
}Sources: OfficeScrubC2R-Native.cs:620-767, docs/OPTIMIZATION_ANALYSIS.md:168-180
The C# implementation uses stack allocation and zero-copy techniques to minimize garbage collection pressure and heap allocations.
The GuidHelper class uses Span<char> for GUID manipulation, eliminating StringBuilder allocations and substring copies.
graph LR
subgraph "Original Implementation Heap Allocations"
SB1["StringBuilder sb = new(38)<br/>38 bytes heap"]
Sub1["Substring(0, 8)<br/>8 bytes heap"]
Rev1["Reverse(substring)<br/>8 bytes heap"]
Append1["sb.Append(reversed)<br/>24 total allocations"]
SB1 --> Sub1
Sub1 --> Rev1
Rev1 --> Append1
end
subgraph "Optimized Implementation Stack Only"
Stack["Span<char> result = stackalloc char[38]<br/>38 bytes stack"]
Slice["ReadOnlySpan<char> source = input.AsSpan(0, 8)<br/>Zero allocation"]
Reverse["ReverseToSpan(source, destination)<br/>Zero allocation"]
Stack --> Slice
Slice --> Reverse
end
Before Optimization: OfficeScrubC2R-Native.cs:173-214 used StringBuilder with substring allocations
After Optimization: docs/OfficeScrubC2R-Native-Optimized-Real.cs:188-225 uses stack-allocated Span:
public static string GetExpandedGuid(string compressedGuid)
{
if (string.IsNullOrEmpty(compressedGuid) || compressedGuid.Length != 32)
return null;
try
{
Span<char> result = stackalloc char[38];
result[0] = '{';
result[9] = '-';
result[14] = '-';
result[19] = '-';
result[24] = '-';
result[37] = '}';
ReverseToSpan(compressedGuid.AsSpan(0, 8), result.Slice(1, 8));
ReverseToSpan(compressedGuid.AsSpan(8, 4), result.Slice(10, 4));
ReverseToSpan(compressedGuid.AsSpan(12, 4), result.Slice(15, 4));
// ... remainder of GUID manipulation
return new string(result).ToUpperInvariant();
}
catch
{
return null;
}
}
private static void ReverseToSpan(ReadOnlySpan<char> source, Span<char> destination)
{
for (int i = 0; i < source.Length; i++)
destination[i] = source[source.Length - 1 - i];
}Memory Impact
| Operation | Before (StringBuilder) | After (Span<char>) | Improvement |
|---|---|---|---|
| Heap allocations per call | 24 | 0 | 100% reduction |
| GC pressure | High | Zero | No GC triggered |
| Execution time | 12.4ms | 3.8ms | 3.3x faster |
The decode table for GUID operations is allocated once as a static field rather than on every method call.
docs/OfficeScrubC2R-Native-Optimized-Real.cs:175-185 uses static decode table:
public static class GuidHelper
{
// OPTIMIZATION: Static decode table - allocate once instead of per call
private static readonly byte[] DecodeTable = new byte[128]
{
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0x00,0xff,0xff,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0xff,
0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0xff,0xff,0xff,0x16,0xff,0x17,
0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0xff,0x34,0x35,0x36,
0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,
0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0xff,0x53,0x54,0xff
};The original implementation at OfficeScrubC2R-Native.cs:264-274 allocated this table on every call to GetDecodedGuid.
Savings: 128 bytes per call, zero GC pressure for GUID decoding operations.
The RegistryHelper uses HashSet<string> with StringComparer.OrdinalIgnoreCase for registry key deduplication, providing O(1) insertion and contains operations.
OfficeScrubC2R-Native.cs:402-436 shows HashSet usage:
public string[] EnumerateKeys(RegistryHiveType hive, string subKey)
{
var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
using (var key = OpenKey(GetHiveKey(hive), subKey, false))
{
if (key != null)
{
foreach (var name in key.GetSubKeyNames())
keys.Add(name); // O(1) average case
}
}
}
catch { }
if (_is64Bit)
{
try
{
using (var key = OpenKey(GetHiveKey(hive), GetWow64Key(subKey), false))
{
if (key != null)
{
foreach (var name in key.GetSubKeyNames())
keys.Add(name); // Deduplicates automatically
}
}
}
catch { }
}
return keys.ToArray();
}Complexity Comparison
| Operation | Array + Contains | HashSet | Benefit |
|---|---|---|---|
| Add item | O(n) check + resize | O(1) average | n times faster |
| Deduplicate 100 items | O(n²) = 10,000 ops | O(n) = 100 ops | 100x faster |
Sources: OfficeScrubC2R-Native.cs:169-328, docs/OPTIMIZATION_ANALYSIS.md:69-99, docs/OfficeScrubC2R-Native-Optimized-Real.cs:175-332
The implementation uses StringComparison.OrdinalIgnoreCase instead of ToLower() for case-insensitive comparisons, eliminating temporary string allocations.
graph LR
subgraph "Before: ToLower Pattern"
Input1["string path"]
Lower1["path.ToLower()<br/>Allocates copy"]
PatternLower["pattern.ToLower()<br/>Allocates copy"]
Compare1["string.Contains<br/>Two allocations"]
Input1 --> Lower1
Lower1 --> Compare1
PatternLower --> Compare1
end
subgraph "After: StringComparison"
Input2["string path"]
Compare2["path.Contains(pattern,<br/>StringComparison.OrdinalIgnoreCase)<br/>Zero allocations"]
Input2 --> Compare2
end
Implementation in OfficeScrubOrchestrator
OfficeScrubC2R-Native.cs:1-56 defines the C2R pattern constants, and the optimized implementation at docs/OfficeScrubC2R-Native-Optimized-Real.cs:1351-1359 uses StringComparison:
public bool IsC2RPath(string path)
{
if (string.IsNullOrEmpty(path))
return false;
return OfficeConstants.C2R_PATTERNS.Any(pattern =>
path.Contains(pattern, StringComparison.OrdinalIgnoreCase));
}Performance Impact
| Scenario | ToLower() | StringComparison | Improvement |
|---|---|---|---|
| 1 million comparisons | 847ms | 423ms | 2x faster |
| Memory allocations | 2 million strings | 0 allocations | 100% reduction |
The RegistryHelper also uses StringComparer.OrdinalIgnoreCase for HashSet initialization at OfficeScrubC2R-Native.cs:404:
var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);This ensures case-insensitive deduplication without ToLower() calls on every registry key name.
Sources: docs/OPTIMIZATION_ANALYSIS.md:100-115, docs/OfficeScrubC2R-Native-Optimized-Real.cs:1351-1359
Direct Win32 API calls via P/Invoke eliminate COM interop overhead and provide lower-level control over system operations.
graph TB
subgraph "VBScript COM Path"
VBS["VBScript Runtime"]
IDispatch["IDispatch Interface<br/>Late binding"]
TypeLib["Type Library Lookup"]
COMProxy["COM Proxy/Stub"]
InProc["In-process COM Server"]
Win32API1["Win32 API"]
VBS --> IDispatch
IDispatch --> TypeLib
TypeLib --> COMProxy
COMProxy --> InProc
InProc --> Win32API1
end
subgraph "C# P/Invoke Path"
CSharp["C# Method Call"]
Marshal["CLR Marshaler"]
Win32API2["Win32 API"]
CSharp --> Marshal
Marshal --> Win32API2
end
Layer Count Comparison
| Path | Layers | Example Overhead |
|---|---|---|
| VBScript → WMI → COM → Win32 | 5-6 layers | ~50ms per registry operation |
| C# → P/Invoke → Win32 | 2 layers | ~2ms per registry operation |
The NativeMethods class at OfficeScrubC2R-Native.cs:62-153 declares Win32 APIs with proper marshaling:
Registry APIs
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int RegOpenKeyEx(
UIntPtr hKey,
string lpSubKey,
uint ulOptions,
int samDesired,
out UIntPtr phkResult);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int RegDeleteKeyEx(
UIntPtr hKey,
string lpSubKey,
int samDesired,
uint Reserved);File System APIs
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool MoveFileEx(
string lpExistingFileName,
string lpNewFileName,
int dwFlags);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool RemoveDirectory(string lpPathName);The P/Invoke approach enables proper WOW64 registry redirection control through the KEY_WOW64_64KEY and KEY_WOW64_32KEY flags at OfficeScrubC2R-Native.cs:145-149:
internal const int KEY_WOW64_64KEY = 0x0100;
internal const int KEY_WOW64_32KEY = 0x0200;
internal const int KEY_READ = 0x20019;
internal const int KEY_WRITE = 0x20006;
internal const int KEY_ALL_ACCESS = 0xF003F;The RegistryHelper uses these flags to access both 32-bit and 64-bit registry views on x64 systems, which is not possible through the managed Microsoft.Win32.Registry API alone.
The MoveFileEx API with MOVEFILE_DELAY_UNTIL_REBOOT flag enables scheduling file/directory deletion on next boot, which has no equivalent in managed .NET APIs.
OfficeScrubC2R-Native.cs:689-697 uses this for locked files:
public void ScheduleDeleteOnReboot(string filePath)
{
try
{
NativeMethods.MoveFileEx(filePath, null, NativeMethods.MOVEFILE_DELAY_UNTIL_REBOOT);
_pendingDeletes.Add(filePath);
}
catch { }
}Sources: OfficeScrubC2R-Native.cs:62-153, docs/OPTIMIZATION_ANALYSIS.md:220-245
Benchmarks were conducted on Windows 10 x64 with Office 365 C2R installed, comparing the VBScript baseline against the optimized C# implementation.
Measures the time to perform 10,000 GUID expansion operations (compressed → expanded format).
| Implementation | Time | Allocations | Performance |
|---|---|---|---|
| Original (StringBuilder) | 12.4ms | 240,000 objects | Baseline |
| Optimized (Span<char>) | 3.8ms | 0 objects | 3.3x faster |
The optimized implementation uses stack allocation at docs/OfficeScrubC2R-Native-Optimized-Real.cs:188-225 with Span<char> result = stackalloc char[38].
Sources: docs/OPTIMIZATION_ANALYSIS.md:268-276
Measures the time to enumerate 100 registry keys with WOW64 dual-view support.
| Implementation | Time | Method |
|---|---|---|
| VBScript (WMI StdRegProv) | 2,487ms | COM automation |
| C# (Win32 P/Invoke) | 82ms | Direct RegOpenKeyEx |
| Improvement | 30x faster | Native API |
The performance gain comes from eliminating COM overhead and using direct Win32 API calls at OfficeScrubC2R-Native.cs:70-86.
Sources: docs/OPTIMIZATION_ANALYSIS.md:277-285
Measures the time to delete a directory tree with 500 files and 50 subdirectories.
| Implementation | Time | Method |
|---|---|---|
| VBScript (FSO) | 15,200ms | Sequential DeleteFile |
| C# (Parallel) | 4,800ms | Parallel Task.Run |
| C# (cmd.exe rd) | 980ms | Kernel batch deletion |
| Improvement | 15.5x faster | Optimized strategy |
The hybrid approach at OfficeScrubC2R-Native.cs:653-687 tries cmd.exe rd /s /q first, falling back to managed deletion.
Sources: docs/OPTIMIZATION_ANALYSIS.md:250-260
Measures 1 million case-insensitive string comparisons.
| Method | Time | Allocations |
|---|---|---|
ToLower() |
847ms | 2 million strings |
StringComparison.OrdinalIgnoreCase |
423ms | 0 allocations |
| Improvement | 2x faster | Zero allocation |
Implementation at docs/OfficeScrubC2R-Native-Optimized-Real.cs:1351-1359 uses StringComparison.OrdinalIgnoreCase.
Sources: docs/OPTIMIZATION_ANALYSIS.md:287-294
Real-world complete Office removal operation on Windows 10 with Office 365 ProPlus installed.
| Component | VBScript | PowerShell/C# | Speedup |
|---|---|---|---|
| Detection (Stage 0) | 45s | 2s | 22.5x |
| Uninstall (Stage 1) | 180s | 15s | 12x |
| Cleanup (Stage 2) | 135s | 8s | 16.9x |
| Summary (Stage 3) | 5s | 1s | 5x |
| Total | 365s (6m 5s) | 26s | 14x faster |
The aggregate improvement varies from 10-50x depending on system state, number of installed products, and file system latency.
Sources: docs/CHANGELOG.md:126-130, README.md:134-142
The 10-50x performance improvement is achieved through a multi-layered optimization strategy:
| Optimization | Component | Speedup | Key Technique |
|---|---|---|---|
| Native code execution | OfficeScrubNative.dll | 10x base | Compiled C# vs interpreted VBScript |
| Registry operations | RegistryHelper | 30x | Win32 P/Invoke, WOW64 dual-view |
| File operations | FileHelper | 15x | Parallel tasks, cmd.exe optimization |
| GUID operations | GuidHelper | 3.3x | Span<char> stack allocation |
| String comparisons | All classes | 2x | StringComparison.OrdinalIgnoreCase |
| Process termination | ProcessHelper | Parallel | Task.Run concurrent execution |
The architecture maintains 100% functional compatibility with the original VBScript implementation while delivering dramatic performance improvements through careful selection of APIs, memory management strategies, and concurrency patterns.
Sources: OfficeScrubC2R-Native.cs:1-1821, docs/OPTIMIZATION_ANALYSIS.md:1-349, docs/OfficeScrubC2R-Native-Optimized-Real.cs:1-1388, README.md:6-20