Repository navigation
PowerShell Components
Relevant source files
The following files were used as context for generating this wiki page:
This document describes the PowerShell layer components that provide the user interface and orchestration for OfficeScrubC2R. The PowerShell layer is intentionally lightweight, serving as a thin wrapper around the native C# library (OfficeScrubNative.dll). All performance-critical operations are delegated to the native layer, which is documented in Native Performance Layer.
The PowerShell layer consists of two primary files:
| File | Lines | Purpose | Key Responsibilities |
|---|---|---|---|
OfficeScrubC2R.psm1 |
99 | Module Definition | DLL loading, orchestrator creation, helper function exports |
OfficeScrubC2R.psd1 |
158 | Module Manifest | Metadata, version info, PowerShell Gallery compatibility, exported functions |
The architecture is designed for simplicity and performance:
- Minimal PowerShell Code: The module contains only 5 exported functions that wrap native operations
-
Direct DLL Integration: All cleanup logic resides in the compiled
OfficeScrubNative.dll - Dynamic Assembly Loading: The native assembly is loaded on-demand with smart caching
- Cross-Platform Support: Compatible with both PowerShell Desktop and PowerShell Core
Sources: OfficeScrubC2R.psm1:1-99, OfficeScrubC2R.psd1:1-158
graph TB
subgraph "Legacy Architecture (v2.x)"
old_psm1["OfficeScrubC2R.psm1<br/>233 lines"]
old_ps1["OfficeScrubC2R.ps1<br/>1009 lines<br/>Three-stage pipeline"]
old_util["OfficeScrubC2R-Utilities.psm1<br/>1091 lines<br/>45+ functions"]
old_dll["OfficeScrubNative.dll<br/>Optional performance layer"]
old_psm1 --> old_util
old_psm1 --> old_ps1
old_ps1 --> old_util
old_util -.optional.-> old_dll
end
subgraph "Current Architecture (v3.0+)"
new_psm1["OfficeScrubC2R.psm1<br/>99 lines<br/>5 functions"]
new_psd1["OfficeScrubC2R.psd1<br/>158 lines<br/>Manifest"]
new_dll["OfficeScrubNative.dll<br/>Required native layer"]
orch["OfficeScrubOrchestrator<br/>Central coordinator"]
new_psm1 --> new_dll
new_dll --> orch
new_psd1 -.defines.-> new_psm1
end
arrow["Migration: v2.x → v3.0"]
old_psm1 -.-> arrow
old_ps1 -.-> arrow
old_util -.-> arrow
arrow -.-> new_dll
note1["Removed:<br/>- Complex PowerShell orchestration<br/>- 45+ utility functions<br/>- Script-based cleanup logic"]
note2["Consolidated:<br/>- All logic moved to C# DLL<br/>- PowerShell is thin wrapper<br/>- 10-50x performance improvement"]
old_util -.-> note1
new_dll -.-> note2
Architectural Evolution
Version 3.0 represents a fundamental architectural shift. The legacy implementation used extensive PowerShell scripting (2,300+ lines) with optional native acceleration. The current implementation inverts this model: all cleanup logic resides in the compiled C# library, with PowerShell providing only a minimal interface layer. This change delivers:
- Performance: 10-50x improvement through native code execution
- Maintainability: Single source of truth for business logic
- Simplicity: 99 lines of PowerShell vs. 2,300+ lines previously
- Reliability: Compiled code with strong typing and error handling
Sources: OfficeScrubC2R.psm1:1-99, OfficeScrubC2R.psd1:124-137
sequenceDiagram
participant User
participant PSGallery as "PowerShell Gallery"
participant psm1 as "OfficeScrubC2R.psm1"
participant Assembly as "System.Reflection.Assembly"
participant DLL as "OfficeScrubNative.dll"
participant Orch as "OfficeScrubOrchestrator"
User->>PSGallery: "Install-Module OfficeScrubC2R"
PSGallery-->>User: "Module files downloaded"
User->>psm1: "Import-Module OfficeScrubC2R"
psm1->>psm1: "Initialize module variables<br/>Lines 6-8"
Note over psm1: "$script:ModuleRoot = $PSScriptRoot<br/>$script:NativeAssemblyPath = Join-Path...<br/>$script:NativeAssembly = $null"
psm1->>psm1: "Export-ModuleMember<br/>Line 98"
Note over psm1: "5 functions exported:<br/>Import-OfficeScrubNative<br/>New-OfficeScrubOrchestrator<br/>Test-OfficeC2RPath<br/>Test-OfficeProductScope<br/>Get-OfficeScrubHelpers"
psm1-->>User: "Module ready (DLL not loaded)"
User->>psm1: "New-OfficeScrubOrchestrator"
psm1->>psm1: "Import-OfficeScrubNative<br/>Line 44"
psm1->>Assembly: "GetAssemblies()<br/>Line 27-28"
Assembly-->>psm1: "Check if already loaded"
alt "DLL not loaded"
psm1->>Assembly: "LoadFrom(resolvedPath)<br/>Line 31"
Assembly->>DLL: "Load assembly"
DLL-->>Assembly: "Assembly loaded"
Assembly-->>psm1: "$script:NativeAssembly"
else "DLL already loaded"
Assembly-->>psm1: "Cached assembly"
end
psm1->>Orch: "new([Environment]::Is64BitOperatingSystem)<br/>Line 45"
Orch-->>psm1: "Orchestrator instance"
psm1-->>User: "Return orchestrator"
Note over User,Orch: "Orchestrator provides access to:<br/>Registry, Files, Processes, Shell,<br/>WindowsInstaller, TypeLib,<br/>License, Services helpers"
Module Loading Sequence
The current architecture uses lazy loading for optimal performance:
- Module Import - OfficeScrubC2R.psm1:6-8 initializes module-level variables for assembly paths
- Function Export - OfficeScrubC2R.psm1:98 exports 5 functions immediately without loading the DLL
-
On-Demand DLL Loading - DLL is loaded only when
New-OfficeScrubOrchestratororImport-OfficeScrubNativeis called - Assembly Caching - OfficeScrubC2R.psm1:23-28 checks if assembly is already loaded to avoid duplicate loading
- Assembly Resolution - OfficeScrubC2R.psm1:17-21 resolves the full path and validates DLL existence
- Orchestrator Creation - OfficeScrubC2R.psm1:45 instantiates the orchestrator with automatic 64-bit detection
This lazy loading pattern ensures:
- Fast module import times (no DLL compilation)
- Memory efficiency (DLL loaded only when needed)
- Multi-load safety (assembly cached after first load)
Sources: OfficeScrubC2R.psm1:6-46
graph TB
subgraph "PowerShell Interface Layer"
import["Import-OfficeScrubNative()<br/>psm1:10-35"]
new_orch["New-OfficeScrubOrchestrator()<br/>psm1:37-46"]
test_path["Test-OfficeC2RPath()<br/>psm1:48-60"]
test_scope["Test-OfficeProductScope()<br/>psm1:62-74"]
get_helpers["Get-OfficeScrubHelpers()<br/>psm1:76-96"]
end
subgraph "Native Assembly: OfficeScrubNative.dll"
orch["OfficeScrubOrchestrator<br/>Central coordinator"]
subgraph "Helper Classes"
reg["RegistryHelper<br/>Registry operations"]
file["FileHelper<br/>File system operations"]
proc["ProcessHelper<br/>Process management"]
shell["ShellHelper<br/>Shell integration"]
wi["WindowsInstallerHelper<br/>MSI metadata"]
tl["TypeLibHelper<br/>COM type libraries"]
lic["LicenseHelper<br/>OSPP/SPP operations"]
svc["ServiceHelper<br/>Service management"]
end
end
subgraph "User Code"
user_script["PowerShell Script<br/>or Interactive Session"]
end
user_script -->|"1. Create orchestrator"| new_orch
new_orch -->|"Loads if needed"| import
import -->|"Assembly.LoadFrom()"| orch
new_orch -->|"new OfficeScrubOrchestrator(is64Bit)"| orch
user_script -->|"2. Get helpers"| get_helpers
get_helpers -->|"Access properties"| orch
orch --> reg
orch --> file
orch --> proc
orch --> shell
orch --> wi
orch --> tl
orch --> lic
orch --> svc
user_script -->|"3. Test paths/scope"| test_path
user_script -->|"3. Test paths/scope"| test_scope
test_path -->|"orchestrator.IsC2RPath(path)"| orch
test_scope -->|"orchestrator.IsInScope(productCode)"| orch
get_helpers -->|"Returns PSCustomObject with:"| helpers_obj["[pscustomobject]@{<br/>RegistryHelper = $orch.Registry<br/>FileHelper = $orch.Files<br/>ProcessHelper = $orch.Processes<br/>ShellHelper = $orch.Shell<br/>WindowsInstallerHelper = $orch.WindowsInstaller<br/>TypeLibHelper = $orch.TypeLib<br/>LicenseHelper = $orch.License<br/>ServiceHelper = $orch.Services<br/>}"]
Bridge Pattern Implementation
The PowerShell layer acts as a bridge between user scripts and native C# operations:
-
Assembly Loading -
Import-OfficeScrubNativeOfficeScrubC2R.psm1:10-35 handles DLL loading with caching -
Orchestrator Creation -
New-OfficeScrubOrchestratorOfficeScrubC2R.psm1:37-46 creates the central coordinator -
Helper Access -
Get-OfficeScrubHelpersOfficeScrubC2R.psm1:76-96 exposes all 8 helper classes as a PSCustomObject -
Validation Methods -
Test-OfficeC2RPathandTest-OfficeProductScopeprovide path/product validation - Type Safety - PowerShell functions accept strongly-typed orchestrator parameters OfficeScrubC2R.psm1:52,66,79
Sources: OfficeScrubC2R.psm1:10-96
The main script implements the three-stage execution pipeline and contains the core business logic. It is dot-sourced into the module scope at OfficeScrubC2R.psm1:21.
graph TD
main["Main()<br/>Lines 924-1001"]
main --> init["Initialize-Script()<br/>Lines 87-136"]
main --> find["Find-InstalledOfficeProducts()<br/>Lines 138-163"]
main --> uninstall["Uninstall-OfficeProducts()<br/>Lines 187-247"]
main --> cleanreg["Clean-OfficeRegistry()<br/>Lines 497-546"]
main --> cleanup["Complete-Cleanup()<br/>Lines 660-844"]
main --> summary["Show-Summary()<br/>Lines 867-918"]
find --> ensure["Ensure-ValidWIMetadata()<br/>Lines 165-185"]
uninstall --> clearlic["Clear-OfficeLicenses()<br/>util:773-798"]
uninstall --> stopproc1["Stop-OfficeProcesses()<br/>util:549-562"]
uninstall --> removetasks["Remove-ScheduledTasks()<br/>Lines 602-656"]
uninstall --> clearshort1["Clear-Shortcuts()<br/>util:949-1005"]
uninstall --> uninstallc2r["Uninstall-OfficeC2R()<br/>Lines 249-282"]
uninstall --> removepub["Remove-PublishedComponents()<br/>Lines 315-378"]
uninstall --> removec2rreg["Remove-C2RRegistryData()<br/>Lines 380-400"]
uninstall --> uninstallmsi["Uninstall-MSIProducts()<br/>Lines 421-491"]
uninstallc2r --> buildxml["Build-RemoveXml()<br/>Lines 284-294"]
uninstallc2r --> downloadodt["Download-ODT()<br/>Lines 296-313"]
cleanreg --> clearoffice["Clear-OfficeHKLM()<br/>Lines 548-570"]
cleanreg --> clearrun["Clear-RunKeyEntries()<br/>Lines 572-585"]
cleanreg --> cleararp["Clear-ARPEntries()<br/>Lines 587-598"]
cleanreg --> clearwi["Clear-WindowsInstallerMetadata()<br/>util:804-831"]
cleanreg --> cleartype["Clear-TypeLibRegistrations()<br/>util:837-849"]
cleanup --> stopproc2["Stop-OfficeProcesses()"]
cleanup --> removetasks2["Remove-ScheduledTasks()"]
cleanup --> removesvc["Remove-Service()<br/>util:747-767"]
cleanup --> clearshort2["Clear-Shortcuts()"]
cleanup --> removeempty["Remove-EmptyFolders()<br/>Lines 846-863"]
Stage 0: Detection (lines 138-163) validates Windows Installer metadata integrity before scanning for installed products.
Stage 1: Uninstall (lines 187-247) coordinates license cleanup, process termination, ODT-based C2R removal, and MSI product uninstallation.
Stage 2: Cleanup (lines 497-844) performs comprehensive registry cleanup and file system cleanup with reboot handling for locked files.
Stage 3: Summary (lines 867-918) displays results, logs detailed error information, and handles reboot prompts.
Sources: OfficeScrubC2R.ps1:87-1001
The main script accepts 12 parameters defined at OfficeScrubC2R.ps1:62-75:
[CmdletBinding()]
param(
[switch]$Quiet, # Minimal output
[switch]$DetectOnly, # Detection mode
[switch]$Force, # Skip confirmation
[switch]$RemoveAll, # Remove all products
[switch]$KeepLicense, # Preserve licenses
[switch]$Offline, # Skip ODT download
[switch]$ForceArpUninstall,
[switch]$ClearTaskBand,
[switch]$UnpinMode, # Unpin shortcuts only
[switch]$SkipSD, # Skip scheduled deletion
[switch]$NoElevate, # No elevation attempt
[string]$LogPath # Custom log location
)
These parameters are copied to script-scope variables at OfficeScrubC2R.ps1:87-136 via Initialize-Script(), allowing them to be accessed by all functions in the script.
Sources: OfficeScrubC2R.ps1:62-75, OfficeScrubC2R.ps1:87-136
The module definition provides the public API through Invoke-OfficeScrubC2R and manages module loading.
graph LR
user["User calls<br/>Invoke-OfficeScrubC2R"]
validate["Parameter Validation<br/>Lines 161-173"]
shouldprocess["ShouldProcess Check<br/>Line 177"]
setvars["Set script: Variables<br/>Lines 180-191"]
callmain["Call Main()<br/>Line 194"]
return["Return Exit Code<br/>Line 197"]
user --> validate
validate --> shouldprocess
shouldprocess --> setvars
setvars --> callmain
callmain --> return
return --> user
validate -.elevation check.-> admin["Test Admin Privileges<br/>Lines 161-168"]
validate -.version check.-> psver["Test PS Version >= 5.1<br/>Lines 171-173"]
The function at OfficeScrubC2R.psm1:118-209 implements:
-
Privilege Validation - Checks administrator rights unless
-NoElevatespecified (lines 161-168) - Version Validation - Requires PowerShell 5.1+ (lines 171-173)
-
ShouldProcess Support - Implements
-WhatIfand-Confirm(line 177) - Variable Bridge - Copies parameters to script-scope for main script access (lines 180-191)
-
Main Execution - Calls
Main()from the dot-sourced script (line 194)
Two aliases are defined at OfficeScrubC2R.psm1:120:
Remove-OfficeC2RUninstall-OfficeC2R
These provide intuitive alternative names for the primary function and are exported at OfficeScrubC2R.psm1:218-220.
Sources: OfficeScrubC2R.psm1:118-209, OfficeScrubC2R.psm1:212-221
The utilities module provides 45+ functions organized into functional categories. All functions delegate performance-critical operations to $script:Orchestrator.
| Category | Functions | Purpose |
|---|---|---|
| Environment |
Initialize-Environment, Get-SystemInfo, Test-IsElevated
|
System detection and orchestrator setup |
| Logging |
Initialize-Log, Write-Log, Write-LogHeader, Write-LogSubHeader, Write-LogOnly, Close-Log
|
Comprehensive logging infrastructure |
| Error Handling |
Set-ErrorCode, Clear-ErrorCode, Set-ReturnValue
|
Bitmask-based error code management |
| Registry |
Get-RegistryValue, Set-RegistryValue, Remove-RegistryKey, Remove-RegistryValue, Get-RegistryKeys, Get-RegistryValues, Test-RegistryKeyExists
|
Registry operations with WOW64 support |
| File System |
Remove-FolderRecursive, Remove-FileForced, Add-PendingFileDelete
|
File operations with reboot scheduling |
| Process |
Stop-OfficeProcesses, Test-ProcessRunning
|
Process management |
| Detection |
Get-InstalledOfficeProducts, Test-IsC2R, Test-ProductInScope
|
Product detection and filtering |
| Services | Remove-Service |
Service management |
| Licensing | Clear-OfficeLicenses |
OSPP/SPP operations |
| Windows Installer | Clear-WindowsInstallerMetadata |
MSI metadata cleanup |
| TypeLib | Clear-TypeLibRegistrations |
COM type library cleanup |
| Shell |
Clear-ShellIntegration, Clear-Shortcuts
|
Shell extension and shortcut cleanup |
All registry functions follow a consistent delegation pattern to the orchestrator:
graph LR
caller["Calling Function"]
regfunc["Registry Function<br/>(e.g., Get-RegistryValue)"]
orch["$script:Orchestrator.Registry"]
regh["RegistryHelper<br/>(Native C#)"]
win32["Win32 APIs<br/>(advapi32.dll)"]
caller --> regfunc
regfunc --> orch
orch --> regh
regh --> win32
win32 -.result.-> regh
regh -.result.-> orch
orch -.result.-> regfunc
regfunc -.result.-> caller
note1["Example:<br/>Get-RegistryValue<br/>(util:382-391)"]
note2["Orchestrator property<br/>(util:391)"]
note3["30x faster than<br/>PowerShell native"]
regfunc -.-> note1
orch -.-> note2
regh -.-> note3
For example, Get-RegistryValue at OfficeScrubC2R-Utilities.psm1:382-391:
return $script:Orchestrator.Registry.GetValue($Hive, $SubKey, $ValueName, $DefaultValue)This pattern provides:
- Consistent API across all registry operations
- 30x performance improvement through native code
- WOW64 registry view support
- Silent error handling with logging
Sources: OfficeScrubC2R-Utilities.psm1:380-471
File operations at OfficeScrubC2R-Utilities.psm1:477-543 implement automatic reboot scheduling for locked files:
graph TD
remove["Remove-FolderRecursive<br/>or Remove-FileForced"]
exists["Check if path exists"]
detectonly["Check $script:DetectOnly"]
callorchestrator["Call $script:Orchestrator.Files"]
success["Operation successful?"]
schedule["Schedule for reboot<br/>Set $script:RebootRequired<br/>Set ERROR_REBOOT_REQUIRED"]
done["Return result"]
remove --> exists
exists -->|No| done
exists -->|Yes| detectonly
detectonly -->|True| done
detectonly -->|False| callorchestrator
callorchestrator --> success
success -->|Yes| done
success -->|No| schedule
schedule --> done
Key features:
- Detect-Only Mode: Previews operations without executing (lines 484-503)
-
Reboot Scheduling: Automatically schedules locked files via
PendingFileRenameOperations(lines 492-495) -
Error Tracking: Sets
ERROR_REBOOT_REQUIREDflag in error code (line 495)
Sources: OfficeScrubC2R-Utilities.psm1:477-543
Get-InstalledOfficeProducts at OfficeScrubC2R-Utilities.psm1:575-717 implements comprehensive product detection across multiple registry locations:
| Detection Source | Registry Path | Lines |
|---|---|---|
| O15 Configuration | SOFTWARE\Microsoft\Office\15.0\ClickToRun\Configuration |
586-599 |
| O15 PropertyBag | SOFTWARE\Microsoft\Office\15.0\ClickToRun\propertyBag |
603-615 |
| Office C2R (QR8+) | SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs |
619-645 |
| Office C2R (QR7) | SOFTWARE\Microsoft\Office\ClickToRun\Configuration |
649-664 |
| O16 Configuration (QR6) | SOFTWARE\Microsoft\Office\16.0\ClickToRun\Configuration |
668-683 |
| ARP Entries | SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall |
687-713 |
The function populates two script-level dictionaries:
-
$script:InstalledSku: Product ID → Version mapping -
$script:C2RSuite: Product ID → Display string mapping
Sources: OfficeScrubC2R-Utilities.psm1:575-717
The utilities module maintains extensive script-level state at OfficeScrubC2R-Utilities.psm1:4-71:
Error Code System (lines 9-22):
$script:ERROR_SUCCESS = 0
$script:ERROR_FAIL = 1
$script:ERROR_REBOOT_REQUIRED = 2
$script:ERROR_USERCANCEL = 4
$script:ERROR_STAGE1 = 8
$script:ERROR_STAGE2 = 16
$script:ERROR_INCOMPLETE = 32
$script:ERROR_DCAF_FAILURE = 64
$script:ERROR_ELEVATION_USERDECLINED = 128
$script:ERROR_ELEVATION = 256
$script:ERROR_SCRIPTINIT = 512
$script:ERROR_RELAUNCH = 1024
$script:ERROR_UNKNOWN = 2048
These constants form a bitmask system where multiple errors can be combined using bitwise OR.
Execution State (lines 28-47):
-
$script:ErrorCode: Current error bitmask -
$script:RebootRequired: Boolean flag for reboot necessity -
$script:IsElevated: Administrator privilege status -
$script:Is64Bit: 64-bit OS detection - Parameter flags:
$script:Quiet,$script:DetectOnly,$script:Force, etc.
Product Collections (lines 49-54):
-
$script:InstalledSku: Detected Office products -
$script:C2RSuite: C2R suite information -
$script:KeepSku: Products to preserve -
$script:DelInUse: Files scheduled for reboot deletion
Environment Paths (lines 56-67):
- Standard folder paths (
ProgramFiles,ProgramData, etc.) - Computed paths (
WICacheDir,ScrubDir,LogDir)
Native Orchestrator (line 70):
-
$script:Orchestrator: TheOfficeScrubOrchestratorinstance providing access to all native helpers
Sources: OfficeScrubC2R-Utilities.psm1:4-71
Error codes use a bitmask pattern implemented at OfficeScrubC2R-Utilities.psm1:333-362:
graph TD
seterror["Set-ErrorCode(ErrorBit)"]
bitwiseor["ErrorCode = ErrorCode OR ErrorBit"]
checkcritical["Is ErrorBit critical?"]
setfail["ErrorCode = ErrorCode OR ERROR_FAIL"]
done1["Return"]
seterror --> bitwiseor
bitwiseor --> checkcritical
checkcritical -->|Yes| setfail
checkcritical -->|No| done1
setfail --> done1
clearerror["Clear-ErrorCode(ErrorBit)"]
bitwisenot["ErrorCode = ErrorCode AND NOT ErrorBit"]
checkclearable["Is ErrorBit clearable?"]
clearfail["ErrorCode = ErrorCode AND NOT ERROR_FAIL"]
done2["Return"]
clearerror --> bitwisenot
bitwisenot --> checkclearable
checkclearable -->|Yes| clearfail
checkclearable -->|No| done2
clearfail --> done2
note1["Critical errors:<br/>DCAF_FAILURE<br/>STAGE2<br/>ELEVATION_USERDECLINED<br/>ELEVATION<br/>SCRIPTINIT"]
note2["Clearable errors:<br/>ELEVATION_USERDECLINED<br/>ELEVATION<br/>SCRIPTINIT"]
checkcritical -.-> note1
checkclearable -.-> note2
This pattern allows:
- Multiple simultaneous error conditions
- Automatic ERROR_FAIL cascading for critical errors
- Individual error flag clearing while maintaining others
Sources: OfficeScrubC2R-Utilities.psm1:333-362
The utilities module exports 45 functions and 39 variables at OfficeScrubC2R-Utilities.psm1:1011-1089, making them available to:
- The main script (dot-sourced into module scope)
- The module definition
- Other PowerShell scripts that import the module with
-Force -Global
The root module exports only 5 functions at OfficeScrubC2R.psm1:212-221:
-
Invoke-OfficeScrubC2R(with aliases) Get-InstalledOfficeProductsTest-IsC2RInitialize-EnvironmentStop-OfficeProcesses
This selective export pattern maintains a clean public API while providing internal functions full access to the utilities.
Sources: OfficeScrubC2R.psm1:212-221, OfficeScrubC2R-Utilities.psm1:1011-1089