Repository navigation
Testing and Validation
Relevant source files
The following files were used as context for generating this wiki page:
This document describes the comprehensive testing and validation procedures for the OfficeScrubC2R project. It covers build verification across PowerShell editions (.NET Framework and .NET Core), module validation for PowerShell Gallery compatibility, and the CI/CD pipeline testing workflow. For information about the build process itself, see Building from Source. For release procedures that follow successful validation, see Release Process.
The project implements a multi-target build verification system that ensures the native C# library compiles and loads correctly in both Windows PowerShell (Desktop edition) and PowerShell Core (Core edition). This verification is critical because the module must support both .NET Framework 4.5+ and .NET Core 7+ runtimes.
The tests/Verify-NativeBuild.ps1 script orchestrates the verification process through three specialized build functions:
flowchart TD
Start["Verify-NativeBuild.ps1<br/>Entry Point"] --> Clean{"-Clean<br/>Parameter?"}
Clean -->|Yes| RemoveArtifacts["Remove artifacts/<br/>Directory"]
Clean -->|No| CheckSource["Check Source File<br/>OfficeScrubC2R-Native.cs"]
RemoveArtifacts --> CheckSource
CheckSource --> ParallelBuild["Invoke Parallel Builds"]
ParallelBuild --> NetFx["Invoke-NetFrameworkBuild<br/>Line 21-71"]
ParallelBuild --> NetCore["Invoke-NetCoreBuild<br/>Line 73-116"]
NetFx --> LocateCsc["Find csc.exe<br/>Framework64 or Framework"]
LocateCsc --> BuildNetFx["Compile with /target:library<br/>/unsafe /optimize+"]
BuildNetFx --> NetFxDLL["OfficeScrubNative.dll<br/>artifacts/netfx/"]
NetCore --> CheckDotnet["Check dotnet SDK<br/>Get-Command dotnet"]
CheckDotnet --> GenerateProj["Generate .csproj<br/>TargetFramework: net7.0-windows"]
GenerateProj --> BuildCore["dotnet build<br/>-nologo -clp:NoSummary"]
BuildCore --> NetCoreDLL["OfficeScrubNative.Core.dll<br/>artifacts/netcore/bin/Debug/"]
NetFxDLL --> EditionCheck{"\$PSEdition"}
NetCoreDLL --> EditionCheck
EditionCheck -->|Desktop| LoadNetFx["Test-AssemblyLoad<br/>Line 118-130<br/>Load .NET Framework DLL"]
EditionCheck -->|Core| LoadCore["Test-AssemblyLoad<br/>Line 118-130<br/>Load .NET Core DLL"]
LoadNetFx --> TestOrch["Instantiate<br/>OfficeScrubOrchestrator"]
LoadCore --> TestOrch
TestOrch --> Success["Verification Complete<br/>Line 142"]
style Start fill:#e1f5ff
style NetFxDLL fill:#fff4e1
style NetCoreDLL fill:#fff4e1
style Success fill:#e1ffe1
Diagram: Build Verification Workflow
Sources: tests/Verify-NativeBuild.ps1:1-143
| Function | Lines | Purpose | Output |
|---|---|---|---|
Invoke-NetFrameworkBuild |
21-71 | Compiles using csc.exe for .NET Framework 4.x |
artifacts/netfx/OfficeScrubNative.dll |
Invoke-NetCoreBuild |
73-116 | Compiles using dotnet build for .NET Core 7+ |
artifacts/netcore/bin/Debug/net7.0-windows/OfficeScrubNative.Core.dll |
Test-AssemblyLoad |
118-130 | Loads assembly and instantiates OfficeScrubOrchestrator
|
Validation that DLL is loadable |
The Invoke-NetFrameworkBuild function locates the C# compiler through the following search pattern:
- Check
Framework64\v4.0.30319\csc.exe(64-bit compiler) - Fall back to
Framework\v4.0.30319\csc.exe(32-bit compiler)
Reference assemblies are validated before compilation:
-
System.Management.dll(required for WMI operations) -
Microsoft.CSharp.dll(required for dynamic operations)
The compilation arguments used are:
/target:library
/out:artifacts/netfx/OfficeScrubNative.dll
/optimize+
/unsafe
/warn:4
/reference:System.Management.dll
/reference:Microsoft.CSharp.dll
OfficeScrubC2R-Native.cs
Sources: tests/Verify-NativeBuild.ps1:21-71
The Invoke-NetCoreBuild function generates a temporary .csproj file with the following configuration:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<OutputType>Library</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>OfficeScrubNative.Core</AssemblyName>
</PropertyGroup>
</Project>The dotnet build command executes with flags to suppress verbose output while maintaining diagnostic information.
Sources: tests/Verify-NativeBuild.ps1:73-116
The Test-AssemblyLoad function performs two critical validations:
-
Assembly Loading: Uses
[System.Reflection.Assembly]::LoadFrom()to verify the DLL is structurally valid -
Orchestrator Instantiation: Creates an instance of
OfficeScrubOrchestratorwith thedryRunparameter set to$trueto verify all class dependencies are correctly resolved
This test ensures that all P/Invoke declarations, helper class dependencies, and static initializers function correctly.
Sources: tests/Verify-NativeBuild.ps1:118-130
The .github/scripts/Validate-Module.ps1 script performs comprehensive validation checks to ensure the module meets PowerShell Gallery publication requirements and functional correctness standards.
flowchart TD
Start["Validate-Module.ps1<br/>Entry Point"] --> ManifestTest["Test 1: Manifest Validation<br/>Test-ModuleManifest<br/>Line 7-13"]
ManifestTest -->|Pass| DLLTest["Test 2: DLL Existence<br/>Test-Path OfficeScrubNative.dll<br/>Line 16-21"]
ManifestTest -->|Fail| RecordFail1["Record: FAIL: Exception"]
DLLTest -->|Pass| ImportTest["Test 3: Module Import<br/>Import-Module -Force<br/>Line 24-26"]
DLLTest -->|Fail| RecordFail2["Record: FAIL: Not found"]
ImportTest -->|Pass| CommandTest["Test 4: Command Export<br/>Get-Command -Module<br/>Line 29-30"]
ImportTest -->|Fail| Skip["Skip Dependent Tests"]
CommandTest --> AliasTest["Test 5: Alias Export<br/>Get-Alias -ModuleName<br/>Line 33-34"]
AliasTest --> FileTest["Test 6: Required Files<br/>LICENSE README.md<br/>CHANGELOG.md CONTRIBUTING.md<br/>Line 43-55"]
FileTest --> HelpTest["Test 7: Help Content<br/>Get-Help Invoke-OfficeScrubC2R<br/>Line 58-69"]
HelpTest --> Display["Display Results Table<br/>Line 72-78"]
Display --> Summary["Calculate Summary<br/>passed/total<br/>Line 81-83"]
Summary --> Result{All Tests<br/>Passed?}
Result -->|Yes| Ready["Module Ready for Publication<br/>Line 86"]
Result -->|No| Review["Review Failed Checks<br/>Line 89"]
RecordFail1 --> Display
RecordFail2 --> Display
Skip --> Display
style Start fill:#e1f5ff
style Ready fill:#e1ffe1
style Review fill:#ffe1e1
Diagram: Module Validation Test Flow
Sources: .github/scripts/Validate-Module.ps1:1-92
| Check | Cmdlet/Method | Success Criteria | Failure Impact |
|---|---|---|---|
| Manifest | Test-ModuleManifest |
Returns valid manifest object | Module cannot be imported |
| DLL | Test-Path |
OfficeScrubNative.dll exists |
Native functions unavailable |
| Import | Import-Module -Force |
No exceptions thrown | Module unusable |
| Commands | Get-Command -Module |
Returns exported functions | API incomplete |
| Aliases | Get-Alias -ModuleName |
Returns defined aliases | User convenience lost |
| Required Files | Test-Path |
All files present | Gallery publication fails |
| Help | Get-Help |
Synopsis exists | Documentation incomplete |
The validation script enforces the presence of these files, which are mandatory for PowerShell Gallery publication:
-
LICENSE- MIT license with Microsoft attribution -
README.md- Project overview and usage instructions -
CHANGELOG.md- Version history and release notes -
CONTRIBUTING.md- Contribution guidelines
Sources: .github/scripts/Validate-Module.ps1:43-55
The script produces a color-coded results table with the following format:
=== OfficeScrubC2R Module Validation ===
Aliases : PASS (2 aliases)
Commands : PASS (5 exported)
DLL : PASS
Help : PASS
Import : PASS
Manifest : PASS (v2.0.0)
Required Files : PASS
=== Summary: 7/7 checks passed ===
Module is ready for publication!
Sources: .github/scripts/Validate-Module.ps1:72-90
The GitHub Actions workflow defined in .github/workflows/ci.yml implements a two-job testing strategy that runs on every push and pull request to main branches.
graph TB
subgraph "Trigger Events"
Push["push: main, master, develop<br/>Line 4-5"]
PR["pull_request: main, master, develop<br/>Line 6-7"]
Manual["workflow_dispatch<br/>Line 8"]
end
subgraph "Job: test (windows-latest)"
TestJob["Test Job<br/>Line 11-63"]
Checkout1["Checkout Code<br/>actions/checkout@v3"]
Setup1["Display PowerShell Version<br/>\$PSVersionTable"]
Build1["Build Native DLL<br/>build.ps1 -Clean"]
CheckDLL["Verify DLL Created<br/>Test-Path OfficeScrubNative.dll"]
Import1["Import Module<br/>Import-Module -Force -Verbose"]
TestCmd["Test Commands<br/>Get-Command -Module"]
TestDetect["Test Detection<br/>Get-InstalledOfficeProducts"]
Manifest1["Validate Manifest<br/>Test-ModuleManifest"]
end
subgraph "Job: validate (windows-latest)"
ValidateJob["Validate Job<br/>Line 64-109"]
Checkout2["Checkout Code<br/>actions/checkout@v3"]
Build2["Build DLL<br/>build.ps1"]
Manifest2["Test Manifest<br/>Test-ModuleManifest"]
Files["Check Required Files<br/>psd1 psm1 dll LICENSE README"]
Blocked["Verify No Zone.Identifier<br/>Get-Item -Stream"]
end
Push --> TestJob
PR --> TestJob
Manual --> TestJob
Push --> ValidateJob
PR --> ValidateJob
Manual --> ValidateJob
TestJob --> Checkout1
Checkout1 --> Setup1
Setup1 --> Build1
Build1 --> CheckDLL
CheckDLL --> Import1
Import1 --> TestCmd
TestCmd --> TestDetect
TestDetect --> Manifest1
ValidateJob --> Checkout2
Checkout2 --> Build2
Build2 --> Manifest2
Manifest2 --> Files
Files --> Blocked
style TestJob fill:#fff4e1
style ValidateJob fill:#fff4e1
Diagram: GitHub Actions CI/CD Pipeline
Sources: .github/workflows/ci.yml:1-109
The test job executes six steps to verify module functionality:
Uses actions/checkout@v3 to clone the repository.
Sources: .github/workflows/ci.yml:16-17
Displays the PowerShell version table to log the execution environment. This helps diagnose edition-specific issues.
Sources: .github/workflows/ci.yml:19-22
Executes build.ps1 -Clean and validates that OfficeScrubNative.dll was created. Throws an exception if the DLL is missing.
Sources: .github/workflows/ci.yml:24-30
Imports the module with -Force and -Verbose flags to ensure clean loading and detailed output logging. Uses Get-Module to verify the module is loaded.
Sources: .github/workflows/ci.yml:32-36
Enumerates exported commands and attempts to call Get-InstalledOfficeProducts in detection mode. The test expects an error due to lack of administrator privileges in the CI environment, which is caught and logged.
try {
Get-InstalledOfficeProducts -ErrorAction Stop
}
catch {
Write-Host "Expected error (no admin): $_"
}Sources: .github/workflows/ci.yml:38-54
Calls Test-ModuleManifest and logs key metadata: version, name, and required PowerShell version.
Sources: .github/workflows/ci.yml:56-62
The validate job focuses on PowerShell Gallery compatibility requirements:
Verifies the presence of five mandatory files:
-
OfficeScrubC2R.psd1(module manifest) -
OfficeScrubC2R.psm1(module script) -
OfficeScrubNative.dll(native library) -
LICENSE(license file) -
README.md(documentation)
Throws an exception if any file is missing.
Sources: .github/workflows/ci.yml:84-99
Scans all files recursively for the Zone.Identifier alternate data stream, which Windows applies to downloaded files. Files with this stream cannot be published to PowerShell Gallery.
Get-ChildItem -Recurse -File | ForEach-Object {
if ((Get-Item $_.FullName -Stream Zone.Identifier -ErrorAction SilentlyContinue)) {
throw "Blocked file found: $($_.FullName)"
}
}Sources: .github/workflows/ci.yml:101-108
PowerShell Gallery enforces specific requirements for module structure, metadata, and file integrity. The validation system ensures compliance with these requirements before publication attempts.
| Requirement | Check Method | Location | Rationale |
|---|---|---|---|
| Valid Manifest | Test-ModuleManifest |
ci.yml:76-82 | Gallery parses manifest for metadata display |
| Module Metadata | Manifest properties | ci.yml:60-62 | Version, author, description required |
| License File | Test-Path LICENSE |
ci.yml:91 | Gallery requires explicit license |
| README File | Test-Path README.md |
ci.yml:92 | Gallery displays README on module page |
| No Blocked Files | Stream check | ci.yml:101-108 | Gallery rejects files with Zone.Identifier |
| DLL Presence | Test-Path *.dll |
ci.yml:90 | Required assemblies must be included |
| Help Content |
Get-Help synopsis |
Validate-Module.ps1:58-69 | Gallery indexes help for search |
The manifest validation ensures the following properties are correctly set:
@{
ModuleVersion = '<semantic-version>'
GUID = '<unique-guid>'
Author = '<author-name>'
Description = '<description>'
PowerShellVersion = '5.1'
FunctionsToExport = @('Invoke-OfficeScrubC2R', ...)
RequiredAssemblies = @('OfficeScrubNative.dll')
PrivateData = @{
PSData = @{
Tags = @('Office', 'Cleanup', 'Uninstall')
LicenseUri = '<license-url>'
ProjectUri = '<project-url>'
}
}
}Sources: .github/workflows/ci.yml:56-62, .github/scripts/Validate-Module.ps1:7-13
While automated testing covers build and import scenarios, certain operations require manual testing on real systems with Office installations.
| Test Scenario | Command | Expected Result | Verification Method |
|---|---|---|---|
| Module Import | Import-Module OfficeScrubC2R |
No errors | Check Get-Module output |
| Detection Only | Invoke-OfficeScrubC2R -DetectOnly |
Lists Office products | Review console output |
| Helper Access | Get-OfficeScrubHelpers |
Returns 8 helper objects | Count returned objects |
| Path Testing | Test-OfficeC2RPath -Path <path> |
Boolean result | Validate against known C2R path |
| Scope Testing | Test-OfficeProductScope -ProductCode <guid> |
Boolean result | Validate against known product |
Manual integration testing should be performed on the following configurations:
PowerShell Editions:
- Windows PowerShell 5.1 (Desktop edition)
- PowerShell 7.x (Core edition)
System Architectures:
- 32-bit Windows (WOW64 registry tests)
- 64-bit Windows (native and WOW64 registry tests)
Office Installations:
- Office 2016 C2R
- Office 2019 C2R
- Microsoft 365 C2R
- Mixed MSI and C2R installations
Test Execution:
- Import the module in clean PowerShell session
- Run detection with
-DetectOnlyflag - Verify detected products match actual installations
- Execute cleanup on test VM or restore point
- Verify registry keys removed (check HKLM and HKCU)
- Verify files deleted or scheduled for reboot
- Verify no false positives on non-Office software
The testing strategy provides coverage across multiple dimensions:
graph LR
subgraph "Build Testing"
CompileNetFx["Compile<br/>.NET Framework"]
CompileCore["Compile<br/>.NET Core"]
LoadTest["Assembly Load Test"]
end
subgraph "Import Testing"
ImportDesktop["Import<br/>Desktop Edition"]
ImportCore["Import<br/>Core Edition"]
Commands["Command Export"]
Aliases["Alias Export"]
end
subgraph "Manifest Testing"
Structure["Structure Validation"]
Metadata["Metadata Validation"]
Dependencies["Dependency Resolution"]
end
subgraph "File Testing"
Existence["File Existence"]
ZoneId["Zone.Identifier Check"]
Permissions["File Permissions"]
end
subgraph "Functional Testing"
Detection["Detection Mode"]
Helpers["Helper Access"]
PathScope["Path/Scope Testing"]
end
CI[GitHub Actions CI] --> CompileNetFx
CI --> CompileCore
CI --> ImportDesktop
CI --> Structure
CI --> Existence
CI --> Detection
Manual[Manual Testing] --> ImportCore
Manual --> PathScope
Manual --> Cleanup[Full Cleanup Testing]
style CI fill:#e1f5ff
style Manual fill:#fff4e1
Diagram: Test Coverage Matrix
Sources: .github/workflows/ci.yml:1-109, tests/Verify-NativeBuild.ps1:1-143, .github/scripts/Validate-Module.ps1:1-92
The following quality gates must pass before any release:
- Build Gate: Both .NET Framework and .NET Core builds succeed
- Import Gate: Module imports without errors in both PowerShell editions
-
Validation Gate: All 7 validation checks in
Validate-Module.ps1pass - Gallery Gate: No blocked files, valid manifest, all required files present
- Function Gate: All exported functions enumerable and callable
These gates are enforced through the CI/CD pipeline and must pass on every commit to protected branches.
Sources: .github/workflows/ci.yml:1-109