Repository navigation
Development Guidelines
Relevant source files
The following files were used as context for generating this wiki page:
This document provides comprehensive coding standards, architectural patterns, and best practices for contributors to the OfficeScrubC2R project. It covers conventions for both PowerShell and C# code, including naming standards, code organization, error handling, performance optimization, and testing patterns.
For information about building the project from source, see Building from Source. For details about the repository structure, see Code Organization. For testing procedures, see Testing and Validation.
The OfficeScrubC2R project follows standard .NET and PowerShell naming conventions to ensure consistency and readability across the codebase.
| Element | Convention | Example |
|---|---|---|
| Classes | PascalCase with descriptive names |
RegistryHelper, WindowsInstallerHelper, OfficeScrubOrchestrator
|
| Methods | PascalCase with action-oriented names |
DeleteKey, EnumerateValues, TerminateProcesses
|
| Properties | PascalCase for public properties |
Registry, Files, Processes, PendingDeletes
|
| Constants | UPPER_CASE with underscores |
OFFICE_ID, PROD_LEN, KEY_WOW64_64KEY
|
| Private Fields | camelCase with underscore prefix |
_regHelper, _pendingDeletes, _is64Bit
|
| Parameters | camelCase |
compressedGuid, directoryPath, processNames
|
| Element | Convention | Example |
|---|---|---|
| Functions | PascalCase with approved verbs |
Import-OfficeScrubNative, Test-OfficeC2RPath
|
| Parameters | PascalCase |
$ProductId, $DetectOnly, $KeepLicense
|
| Variables | camelCase for local, PascalCase for script-scope |
$tempPath, $script:LogDir
|
| Constants | UPPER_CASE with underscores |
$OFFICE_15, $C2R_PATTERN
|
Sources:
C# source files use #region blocks to group related functionality and improve code navigation. This pattern is extensively used in OfficeScrubC2R-Native.cs.
graph TB
subgraph "OfficeScrubC2R-Native.cs File Structure"
File["OfficeScrubC2R-Native.cs"]
File --> RegionEnums["#region Enums and Constants<br/>OfficeConstants class<br/>GUID lengths, paths"]
File --> RegionPInvoke["#region P/Invoke Declarations<br/>NativeMethods class<br/>Win32 API imports"]
File --> RegionOrch["#region OfficeScrubOrchestrator<br/>Central coordinator<br/>IsInScope method"]
File --> RegionReg["#region RegistryHelper<br/>Registry operations<br/>WOW64 support"]
File --> RegionFile["#region FileHelper<br/>File/directory operations<br/>Reboot scheduling"]
File --> RegionProc["#region ProcessHelper<br/>Process termination<br/>Parallel execution"]
File --> RegionWI["#region WindowsInstallerHelper<br/>MSI cleanup<br/>GUID transformations"]
File --> RegionOther["#region Additional Helpers<br/>TypeLib, License, Service<br/>Shell, Guid helpers"]
end
style File fill:#f9f
style RegionOrch fill:#fff4e1
Diagram: C# Source File Organization with Region Blocks
The native library separates concerns into distinct helper classes, each responsible for a specific Windows subsystem:
graph LR
subgraph "Helper Class Organization"
Orchestrator["OfficeScrubOrchestrator<br/>(Central Coordinator)"]
Orchestrator --> Registry["RegistryHelper<br/>KeyExists, DeleteKey<br/>EnumerateKeys, WOW64"]
Orchestrator --> File["FileHelper<br/>DeleteFile, DeleteDirectory<br/>MoveFileEx, PendingDeletes"]
Orchestrator --> Process["ProcessHelper<br/>TerminateProcesses<br/>Parallel Task.Run"]
Orchestrator --> WI["WindowsInstallerHelper<br/>CleanupUpgradeCodes<br/>CleanupProducts"]
Orchestrator --> TLH["TypeLibHelper<br/>UnregisterTypeLib<br/>COM cleanup"]
Orchestrator --> LH["LicenseHelper<br/>OSPP operations<br/>SPP cleanup"]
Orchestrator --> SH["ServiceHelper<br/>StopService<br/>DeleteService"]
Orchestrator --> Shell["ShellHelper<br/>Pin/Unpin operations<br/>Shortcut cleanup"]
Orchestrator --> Guid["GuidHelper<br/>GetExpandedGuid<br/>GetCompressedGuid"]
end
style Orchestrator fill:#fff4e1
Diagram: Separation of Concerns via Helper Classes
Utility functions that don't maintain state are organized as static classes:
-
GuidHelper- GUID encoding/decoding utilities (OfficeScrubC2R-Native.cs) -
OfficeConstants- Product codes, registry paths, constants (OfficeScrubC2R-Native.cs)
Sources:
- docs/memory-bank/guidelines.md:14-24
- OfficeScrubC2R-Native.cs (entire file structure)
The codebase uses a consistent error handling pattern where operations return false or null on failure rather than throwing exceptions. This allows the orchestrator to continue execution even when individual operations fail.
flowchart TD
Start["Method Execution"] --> Try["try block<br/>Perform operation"]
Try -->|"Success"| Return["return true/result"]
Try -->|"Exception"| Catch["catch block<br/>(no specific handling)"]
Catch --> ReturnFalse["return false/null"]
subgraph "Example: RegistryHelper.DeleteKey"
ExStart["DeleteKey(keyPath)"] --> ExTry["try:<br/>RegDeleteKeyEx()"]
ExTry -->|"Success"| ExTrue["return true"]
ExTry -->|"Exception"| ExCatch["catch:<br/>silent"]
ExCatch --> ExFalse["return false"]
end
style Catch fill:#ffe1e1
style ExCatch fill:#ffe1e1
Diagram: Silent Failure Pattern with Try-Catch
Implementation Example: OfficeScrubC2R-Native.cs - RegistryHelper.DeleteKey, FileHelper.DeleteFile, and other operations follow this pattern.
Always use using statements or explicit disposal in finally blocks for resources that implement IDisposable:
// Pattern used in ProcessHelper.TerminateProcesses
using (Process process = Process.GetProcessById(pid))
{
process.Kill();
process.WaitForExit(timeout);
}Provide fallback mechanisms when primary operations fail:
| Primary Operation | Fallback Mechanism |
|---|---|
| Directory.Delete() | cmd.exe rd /s /q
|
| File deletion | Schedule on reboot with MoveFileEx |
| Pre-compiled DLL | Source compilation with csc.exe |
| Native WMI operations | PowerShell cmdlets |
Sources:
- docs/memory-bank/guidelines.md:26-41
- OfficeScrubC2R-Native.cs (error handling throughout)
The helper class pattern is the core architectural design that separates concerns into specialized components.
graph TB
subgraph "Helper Class Pattern"
Orchestrator["OfficeScrubOrchestrator<br/>(Composition Root)"]
subgraph "Initialization"
Constructor["Constructor()"]
Constructor --> CreateHelpers["Create helper instances"]
CreateHelpers --> RegInstance["_regHelper = new RegistryHelper()"]
CreateHelpers --> FileInstance["_fileHelper = new FileHelper()"]
CreateHelpers --> ProcInstance["_processHelper = new ProcessHelper()"]
end
subgraph "Dependency Injection"
Orchestrator --> PropRegistry["Registry Property<br/>get => _regHelper"]
Orchestrator --> PropFiles["Files Property<br/>get => _fileHelper"]
Orchestrator --> PropProcesses["Processes Property<br/>get => _processHelper"]
end
subgraph "Helper Interface"
Helper["Helper Class"]
Helper --> State["Internal State<br/>_is64Bit, _pendingDeletes"]
Helper --> Methods["Public Methods<br/>Operation-specific"]
Helper --> Cache["Caching<br/>HashSet collections"]
end
Orchestrator --> Helper
end
style Orchestrator fill:#fff4e1
style Helper fill:#e1ffe1
Diagram: Helper Class Pattern with Dependency Injection
When adding a new helper class, follow these steps:
- Define the helper class with private constructor fields for dependencies
- Encapsulate related operations in public methods
- Maintain internal state for caching and performance
- Use the silent failure pattern for error handling
- Add to orchestrator as a property with lazy initialization if needed
Example structure:
public class MyNewHelper
{
private readonly SomeDependency _dependency;
private readonly HashSet<string> _cache;
public MyNewHelper(SomeDependency dependency)
{
_dependency = dependency;
_cache = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
public bool PerformOperation(string input)
{
try
{
// Operation logic
_cache.Add(input);
return true;
}
catch
{
return false;
}
}
}Sources:
- docs/memory-bank/guidelines.md:43-49
- OfficeScrubC2R-Native.cs (all helper class implementations)
P/Invoke (Platform Invoke) is used to call Win32 APIs directly for performance-critical operations. All P/Invoke declarations are centralized in the NativeMethods internal static class.
graph TB
subgraph "P/Invoke Pattern"
NativeMethods["internal static class NativeMethods"]
subgraph "Win32 API Declarations"
NativeMethods --> RegAPI["advapi32.dll APIs<br/>RegOpenKeyEx<br/>RegDeleteKeyEx<br/>RegEnumKeyEx"]
NativeMethods --> FileAPI["kernel32.dll APIs<br/>MoveFileEx<br/>GetFileAttributes<br/>SetFileAttributes"]
NativeMethods --> ShellAPI["shell32.dll APIs<br/>SHGetKnownFolderPath<br/>IShellLink"]
end
subgraph "Constants and Enums"
NativeMethods --> Flags["API Flags<br/>KEY_WOW64_64KEY<br/>KEY_WOW64_32KEY<br/>MOVEFILE_DELAY_UNTIL_REBOOT"]
NativeMethods --> Access["Access Rights<br/>KEY_READ<br/>KEY_WRITE<br/>KEY_ALL_ACCESS"]
end
subgraph "Helper Class Usage"
Registry["RegistryHelper"] --> RegAPI
FileHelper["FileHelper"] --> FileAPI
ShellHelper["ShellHelper"] --> ShellAPI
end
end
style NativeMethods fill:#fff4e1
Diagram: P/Invoke Pattern with NativeMethods Class
All Win32 API imports follow this pattern:
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int RegOpenKeyEx(
IntPtr hKey,
string lpSubKey,
int ulOptions,
int samDesired,
out IntPtr phkResult);Key elements:
-
[DllImport]attribute specifying the DLL name -
CharSet = CharSet.Unicodefor proper string marshaling -
SetLastError = trueto enableMarshal.GetLastWin32Error() -
internal static externvisibility and signature -
Proper parameter marshaling with
IntPtr,string, andoutparameters
Constants for Win32 API flags are defined in the NativeMethods class:
| Category | Constants | Usage |
|---|---|---|
| Registry Views |
KEY_WOW64_64KEY = 0x0100KEY_WOW64_32KEY = 0x0200
|
WOW64 registry redirection |
| Access Rights |
KEY_READ = 0x20019KEY_WRITE = 0x20006KEY_ALL_ACCESS = 0xF003F
|
Registry key access |
| File Operations | MOVEFILE_DELAY_UNTIL_REBOOT = 0x4 |
Schedule file deletion on reboot |
Sources:
- docs/memory-bank/guidelines.md:51-55
- OfficeScrubC2R-Native.cs (NativeMethods class)
On 64-bit Windows systems, the registry is split into two views: the native 64-bit view and the redirected 32-bit view (WOW64). Office installations can leave artifacts in both views, so cleanup operations must handle both.
flowchart TD
Start["Registry Operation"] --> CheckBit{"_is64Bit?"}
CheckBit -->|"32-bit System"| SinglePath["Single Path<br/>HKLM\Software\X"]
CheckBit -->|"64-bit System"| DualPath["Dual Path Operations"]
DualPath --> Native["Native 64-bit View<br/>HKLM\Software\X<br/>KEY_WOW64_64KEY flag"]
DualPath --> WOW["WOW64 32-bit View<br/>HKLM\Software\Wow6432Node\X<br/>KEY_WOW64_32KEY flag"]
Native --> NativeOp["RegDeleteKeyEx(..., KEY_WOW64_64KEY)"]
WOW --> WOWOp["RegDeleteKeyEx(..., KEY_WOW64_32KEY)"]
SinglePath --> Cache["Check Cache"]
NativeOp --> Cache
WOWOp --> Cache
Cache -->|"Not in cache"| Execute["Execute + Add to Cache"]
Cache -->|"In cache"| Skip["Skip (already processed)"]
subgraph "GetWow64Key Method"
Transform["Transform Path"]
Transform --> Replace["Replace 'Software\' with<br/>'Software\Wow6432Node\'"]
end
WOW --> Transform
style DualPath fill:#fff4e1
style Cache fill:#e1ffe1
Diagram: WOW64 Registry Dual-Path Handling
The RegistryHelper class implements WOW64 support through:
-
_is64Bitfield - Determines system architecture viaEnvironment.Is64BitOperatingSystem -
GetWow64Key()method - Transforms registry paths for 32-bit view by injectingWow6432Node -
Dual API calls - Each registry operation calls Win32 API twice with different flags:
-
KEY_WOW64_64KEYfor native 64-bit view -
KEY_WOW64_32KEYfor redirected 32-bit view
-
-
Cache deduplication -
HashSet<string>withStringComparer.OrdinalIgnoreCaseprevents redundant operations
Example from OfficeScrubC2R-Native.cs:
private string GetWow64Key(string keyPath)
{
if (!keyPath.Contains(@"Software\", StringComparison.OrdinalIgnoreCase))
return keyPath;
return keyPath.Replace(@"Software\", @"Software\Wow6432Node\",
StringComparison.OrdinalIgnoreCase);
}Sources:
- docs/memory-bank/guidelines.md:57-62
- OfficeScrubC2R-Native.cs (RegistryHelper class)
The OfficeScrubC2R project achieves 10-50x performance improvements over the original VBScript through several optimization techniques.
CPU-intensive operations that can be parallelized use Task.Run() with Task.WaitAll() to execute concurrently:
sequenceDiagram
participant Caller
participant ProcessHelper
participant Tasks["Task.Run() Pool"]
participant Lock["Thread-Safe Collection"]
Caller->>ProcessHelper: TerminateProcesses(processNames)
ProcessHelper->>ProcessHelper: Enumerate processes
loop For each process
ProcessHelper->>Tasks: Task.Run(() => KillProcess(pid))
end
par Parallel Execution
Tasks->>Tasks: Process 1: Kill + WaitForExit
Tasks->>Tasks: Process 2: Kill + WaitForExit
Tasks->>Tasks: Process N: Kill + WaitForExit
end
Tasks->>Lock: lock(terminatedPids) { Add(pid) }
ProcessHelper->>Tasks: Task.WaitAll(allTasks, timeout)
Tasks-->>ProcessHelper: All complete
ProcessHelper-->>Caller: Return terminated PIDs
Diagram: Parallel Process Termination with Task.Run()
Implementation location: OfficeScrubC2R-Native.cs in ProcessHelper.TerminateProcesses
Use StringBuilder with pre-allocated capacity for string building operations:
// Pattern for GUID manipulation
StringBuilder sb = new StringBuilder(capacity: 38);
sb.Append('{');
sb.Append(segment1);
sb.Append('-');
// ...
return sb.ToString();Use LINQ methods like Any() for early termination in collection searches:
// Early termination when any match found
if (officeProcessNames.Any(name =>
process.ProcessName.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
// Found match, no need to continue enumeration
}The system caches frequently accessed data to avoid redundant operations:
| Component | Cached Data | Purpose |
|---|---|---|
| RegistryHelper |
HashSet<string> of processed keys |
Prevent duplicate registry operations |
| FileHelper |
List<string> of pending deletes |
Track reboot-scheduled deletions |
| WindowsInstallerHelper | Expanded GUIDs | Avoid repeated GUID transformations |
Related operations are grouped together for efficiency:
- Registry enumeration - Enumerate all keys once, then filter
- Process termination - Collect all PIDs, then terminate in parallel
-
File deletion - Use
cmd.exe rd /s /qfor large directories (15x faster)
Sources:
- docs/memory-bank/guidelines.md:98-118
- CHANGELOG.md:46-103 (optimization details)
- OfficeScrubC2R-Native.cs (performance patterns throughout)
All public methods validate inputs at entry points:
flowchart TD
MethodEntry["Method Entry"] --> NullCheck{"Input == null?"}
NullCheck -->|"Yes"| ReturnEarly["return false/null"]
NullCheck -->|"No"| EmptyCheck{"string.IsNullOrEmpty?"}
EmptyCheck -->|"Yes"| ReturnEarly
EmptyCheck -->|"No"| LengthCheck{"Valid length?"}
LengthCheck -->|"No"| ReturnEarly
LengthCheck -->|"Yes"| FormatCheck{"Valid format?"}
FormatCheck -->|"No"| ReturnEarly
FormatCheck -->|"Yes"| ProcessInput["Process Input"]
subgraph "GUID Validation Example"
GUIDEntry["GetExpandedGuid(guid)"]
GUIDEntry --> GUIDLen{"guid.Length == 32?"}
GUIDLen -->|"No"| GUIDFail["return null"]
GUIDLen -->|"Yes"| GUIDProcess["Transform GUID"]
end
style ReturnEarly fill:#ffe1e1
style GUIDFail fill:#ffe1e1
Diagram: Input Validation Flow with Early Returns
Validation checklist:
- Check for null or empty inputs
- Validate string lengths and formats
- Validate GUID formats before transformation
- Check array bounds before access
- Validate registry key paths
The system assumes administrator privileges are available but implements graceful degradation:
// Pattern: Attempt operation with appropriate access rights
int samDesired = KEY_ALL_ACCESS; // Requires admin
if (RegOpenKeyEx(hive, subKey, 0, samDesired, out IntPtr hKey) != 0)
{
// Fall back to read-only if write fails
samDesired = KEY_READ;
RegOpenKeyEx(hive, subKey, 0, samDesired, out hKey);
}Registry access rights used:
-
KEY_READ = 0x20019- Read-only access -
KEY_WRITE = 0x20006- Write access -
KEY_ALL_ACCESS = 0xF003F- Full access (requires admin)
Use framework methods for safe string manipulation:
| Operation | Safe Method | Purpose |
|---|---|---|
| Process name extraction | Path.GetFileNameWithoutExtension() |
Handles invalid path chars |
| Substring operations | Bounds checking before substring | Prevent IndexOutOfRangeException |
| Case-insensitive compare | StringComparison.OrdinalIgnoreCase |
Culture-independent comparison |
| Path combination | Path.Combine() |
Handles separators correctly |
Sources:
- docs/memory-bank/guidelines.md:120-138
- OfficeScrubC2R-Native.cs (validation throughout)
The codebase uses defensive programming techniques to prevent errors:
// Return early on invalid inputs
if (string.IsNullOrEmpty(keyPath))
return false;
// Use null-conditional operators
process?.Kill();
// Implement bounds checking
if (index < 0 || index >= array.Length)
return null;Helper methods are designed to return success/failure indicators that enable comprehensive logging by the PowerShell layer:
graph LR
subgraph "Logging Architecture"
Helper["Helper Method"] --> Return["Returns bool/null"]
Return --> PS["PowerShell Layer"]
PS --> Check{"Success?"}
Check -->|"true"| LogSuccess["Write-Verbose: Success"]
Check -->|"false"| LogError["Write-Warning: Failed"]
Helper --> State["Exposes State"]
State --> Pending["PendingDeletes property"]
State --> Lists["Collection properties"]
PS --> Query["Query state after operation"]
Query --> Report["Generate detailed report"]
end
style Helper fill:#fff4e1
style LogError fill:#ffe1e1
Diagram: Logging Integration Points
Logging pattern:
- Helper methods return
boolfor success/failure - Helper classes expose collections for post-operation querying
- PowerShell layer logs detailed operation results
- Error details available via return values and state properties
The architecture enables unit testing through:
- Single Responsibility Principle - Each helper class has one focus
- Dependency Injection - Orchestrator composes helpers
-
Stateless utility methods -
GuidHelpermethods are pure functions - Mockable dependencies - Interfaces can be added for testing
Sources:
- docs/memory-bank/guidelines.md:140-158
- OfficeScrubC2R.psm1 (logging integration)
All public classes and methods require XML documentation:
/// <summary>
/// Deletes a registry key and all its subkeys.
/// </summary>
/// <param name="keyPath">Full path to the registry key (e.g., "HKLM\Software\Microsoft\Office")</param>
/// <returns>True if the key was successfully deleted or doesn't exist; false on error</returns>
public bool DeleteKey(string keyPath)
{
// Implementation
}Required XML tags:
-
<summary>- Brief description of the class/method -
<param>- Description of each parameter -
<returns>- Description of return value -
<remarks>- Additional usage notes (optional) -
<example>- Usage examples for complex operations (optional)
Inline comments should be used sparingly, only for:
- Complex algorithms - Explain non-obvious logic
- Win32 API usage - Document parameter meanings and error codes
- Business logic - Explain Office-specific requirements
- Known limitations - Document edge cases or assumptions
Good comment:
// WOW64 redirection: On 64-bit systems, Office can install in either
// the native 64-bit view or the 32-bit redirected view. We must check both.
if (_is64Bit)
{
// Check native view with KEY_WOW64_64KEY flag
// Check WOW64 view with KEY_WOW64_32KEY flag
}Poor comment (self-documenting code is better):
// Increment counter
counter++; // DON'T DO THISMagic numbers and constants must be explained:
// Office product ID length is always 4 characters (e.g., "O365", "2019")
public const int PROD_LEN = 4;
// Registry key for pending file renames on reboot
private const string PENDING_FILE_RENAME_KEY =
@"SYSTEM\CurrentControlSet\Control\Session Manager";Documentation for:
- Purpose and source of magic numbers
- Significance of registry paths
- Known GUID patterns and their meanings
- Win32 API constants and their values
Sources:
PowerShell functions follow this template:
function Verb-Noun {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$RequiredParameter,
[Parameter()]
[int]$OptionalParameter = 0,
[switch]$SwitchParameter
)
begin {
# Initialization
Write-Verbose "Starting Verb-Noun"
}
process {
# Main logic
try {
# Operation
}
catch {
Write-Error "Error: $_"
}
}
end {
# Cleanup
Write-Verbose "Completed Verb-Noun"
}
}All exported functions require comment-based help:
<#
.SYNOPSIS
Brief description of the function
.DESCRIPTION
Detailed description of what the function does
.PARAMETER ParameterName
Description of what the parameter does
.EXAMPLE
Verb-Noun -ParameterName "Value"
Description of what this example does
.NOTES
Additional information (author, version, etc.)
#>Use only approved PowerShell verbs (check with Get-Verb):
| Category | Common Verbs | Usage |
|---|---|---|
| Common |
Get, Set, New, Remove
|
Basic CRUD operations |
| Data |
Import, Export, Convert
|
Data transformation |
| Diagnostic |
Test, Trace, Measure
|
Testing and diagnostics |
| Lifecycle |
Start, Stop, Restart
|
Process/service control |
Sources:
- CONTRIBUTING.md:73-96
- OfficeScrubC2R.psm1 (PowerShell implementation)
The project uses PSScriptAnalyzer for code quality enforcement. Configuration is defined in PSScriptAnalyzerSettings.psd1.
@{
'ExcludeRules' = @('*') # All rules excluded by default
'Rules' = @{
'PSAvoidUsingCmdletAliases' = @{
'allowlist' = @('set', 'get', 'nv', 'Add-Type')
}
}
}Note: The current configuration excludes most rules. Contributors should follow best practices even if not enforced:
- Avoid using cmdlet aliases (except those allowlisted)
- Use proper parameter validation
- Include error handling
- Follow naming conventions
- Add comment-based help
# Install PSScriptAnalyzer
Install-Module -Name PSScriptAnalyzer -Scope CurrentUser
# Analyze module
Invoke-ScriptAnalyzer -Path . -Recurse -Settings .\PSScriptAnalyzerSettings.psd1
# Analyze specific file
Invoke-ScriptAnalyzer -Path .\OfficeScrubC2R.psm1Sources:
Follow this structure for commit messages:
Add feature to detect Office 2021
- Implement detection for Office 2021 C2R
- Add registry key checks for Office 2021
- Update tests to cover Office 2021
Fixes #123
Format:
- First line - Short summary (50 characters max)
- Blank line
- Body - Detailed explanation (wrap at 72 characters)
- Blank line
-
Footer - Reference issues with
Fixes #123orCloses #456
The project follows Semantic Versioning 2.0.0:
graph LR
Version["Version: MAJOR.MINOR.PATCH"]
Version --> Major["MAJOR<br/>Breaking changes<br/>Non-backward compatible"]
Version --> Minor["MINOR<br/>New features<br/>Backward compatible"]
Version --> Patch["PATCH<br/>Bug fixes<br/>Backward compatible"]
subgraph "Example Version History"
V2190["2.19.0<br/>Initial release"]
V2191["2.19.1<br/>Bug fixes"]
V2192["2.19.2<br/>More fixes"]
V220["2.20.0<br/>New features"]
V300["3.0.0<br/>Breaking changes"]
V2190 --> V2191
V2191 --> V2192
V2192 --> V220
V220 --> V300
end
style Major fill:#ffe1e1
style Minor fill:#fff4e1
style Patch fill:#e1ffe1
Diagram: Semantic Versioning Strategy
Version update guidelines:
| Change Type | Example | Version Update |
|---|---|---|
| Breaking change | Remove public function |
2.19.0 → 3.0.0
|
| New feature | Add new helper class |
2.19.0 → 2.20.0
|
| Bug fix | Fix registry deletion |
2.19.0 → 2.19.1
|
| Documentation | Update wiki | No version change |
Sources:
#region MyHelper
public class MyHelper
{
// Private fields with underscore prefix
private readonly SomeDependency _dependency;
private readonly HashSet<string> _cache;
private bool _is64Bit;
// Constructor with dependency injection
public MyHelper(SomeDependency dependency)
{
_dependency = dependency;
_cache = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_is64Bit = Environment.Is64BitOperatingSystem;
}
// Public operation method with validation and error handling
public bool PerformOperation(string input)
{
// Input validation
if (string.IsNullOrEmpty(input))
return false;
// Check cache to avoid redundant operations
if (_cache.Contains(input))
return true;
// Try-catch with silent failure pattern
try
{
// Perform operation
DoWork(input);
// Update cache
_cache.Add(input);
return true;
}
catch
{
return false;
}
}
// Private helper methods
private void DoWork(string input)
{
// Implementation
}
// Public property to expose state
public IReadOnlyCollection<string> ProcessedItems => _cache;
}
#endregionpublic bool DeleteKey(string keyPath)
{
if (string.IsNullOrEmpty(keyPath))
return false;
bool success = false;
// Attempt deletion in native view
success |= DeleteKeyInternal(keyPath, isWow64: false);
// On 64-bit systems, also delete in WOW64 view
if (_is64Bit)
{
string wow64Key = GetWow64Key(keyPath);
success |= DeleteKeyInternal(wow64Key, isWow64: true);
}
return success;
}
private bool DeleteKeyInternal(string keyPath, bool isWow64)
{
int samDesired = isWow64 ? KEY_WOW64_32KEY : KEY_WOW64_64KEY;
try
{
// P/Invoke call with appropriate flags
IntPtr hKey = GetHiveKey(keyPath);
string subKey = GetSubKeyPath(keyPath);
int result = RegDeleteKeyEx(hKey, subKey, samDesired, 0);
return result == 0;
}
catch
{
return false;
}
}Sources:
- docs/memory-bank/guidelines.md:14-179 (all patterns)
- OfficeScrubC2R-Native.cs (complete implementations)
This development guidelines document provides the foundation for maintaining code quality and consistency across the OfficeScrubC2R project. Contributors should refer to these patterns and standards when adding new features or modifying existing code. For specific implementation details, refer to the source files cited throughout this document.