Skip to content

Testing and Validation

Calvindd2f edited this page Jan 14, 2026 · 1 revision

Testing and Validation

Relevant source files

The following files were used as context for generating this wiki page:

Purpose and Scope

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.


Build Verification Strategy

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.

Verification Script Architecture

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
Loading

Diagram: Build Verification Workflow

Sources: tests/Verify-NativeBuild.ps1:1-143

Build Function Implementations

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

.NET Framework Build Process

The Invoke-NetFrameworkBuild function locates the C# compiler through the following search pattern:

  1. Check Framework64\v4.0.30319\csc.exe (64-bit compiler)
  2. 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

.NET Core Build Process

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

Assembly Load Testing

The Test-AssemblyLoad function performs two critical validations:

  1. Assembly Loading: Uses [System.Reflection.Assembly]::LoadFrom() to verify the DLL is structurally valid
  2. Orchestrator Instantiation: Creates an instance of OfficeScrubOrchestrator with the dryRun parameter set to $true to 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


Module Validation

The .github/scripts/Validate-Module.ps1 script performs comprehensive validation checks to ensure the module meets PowerShell Gallery publication requirements and functional correctness standards.

Validation Test Suite

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
Loading

Diagram: Module Validation Test Flow

Sources: .github/scripts/Validate-Module.ps1:1-92

Validation Checks Summary

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

Required Files for PowerShell Gallery

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

Output Formatting

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


CI/CD Testing Pipeline

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.

Workflow Structure

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
Loading

Diagram: GitHub Actions CI/CD Pipeline

Sources: .github/workflows/ci.yml:1-109

Test Job Breakdown

The test job executes six steps to verify module functionality:

Step 1: Checkout Code

Uses actions/checkout@v3 to clone the repository.

Sources: .github/workflows/ci.yml:16-17

Step 2: Setup PowerShell

Displays the PowerShell version table to log the execution environment. This helps diagnose edition-specific issues.

Sources: .github/workflows/ci.yml:19-22

Step 3: Build Native DLL

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

Step 4: Test Module Import

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

Step 5: Test Module Functions

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

Step 6: Validate Manifest

Calls Test-ModuleManifest and logs key metadata: version, name, and required PowerShell version.

Sources: .github/workflows/ci.yml:56-62

Validate Job Breakdown

The validate job focuses on PowerShell Gallery compatibility requirements:

Required Files Check

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

Zone.Identifier Stream Check

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 Compatibility Testing

PowerShell Gallery enforces specific requirements for module structure, metadata, and file integrity. The validation system ensures compliance with these requirements before publication attempts.

Gallery Requirements Matrix

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

Module Manifest Structure

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


Manual Testing Procedures

While automated testing covers build and import scenarios, certain operations require manual testing on real systems with Office installations.

Pre-Release Testing Checklist

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

Integration Testing on Target Systems

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:

  1. Import the module in clean PowerShell session
  2. Run detection with -DetectOnly flag
  3. Verify detected products match actual installations
  4. Execute cleanup on test VM or restore point
  5. Verify registry keys removed (check HKLM and HKCU)
  6. Verify files deleted or scheduled for reboot
  7. Verify no false positives on non-Office software

Test Coverage and Quality Metrics

The testing strategy provides coverage across multiple dimensions:

Coverage 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
Loading

Diagram: Test Coverage Matrix

Sources: .github/workflows/ci.yml:1-109, tests/Verify-NativeBuild.ps1:1-143, .github/scripts/Validate-Module.ps1:1-92

Quality Gates

The following quality gates must pass before any release:

  1. Build Gate: Both .NET Framework and .NET Core builds succeed
  2. Import Gate: Module imports without errors in both PowerShell editions
  3. Validation Gate: All 7 validation checks in Validate-Module.ps1 pass
  4. Gallery Gate: No blocked files, valid manifest, all required files present
  5. 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

Clone this wiki locally