Skip to content

CI CD Pipeline

Calvindd2f edited this page Jan 14, 2026 · 1 revision

CI/CD Pipeline

Relevant source files

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

This page documents the GitHub Actions CI/CD pipeline that automatically builds, tests, and validates the OfficeScrubC2R module on every push and pull request. The pipeline ensures code quality, PowerShell Gallery compatibility, and module functionality before publication.

For information about the manual build process, see Building from Source. For publication procedures, see Release Process.


Pipeline Overview

The CI/CD pipeline is defined in .github/workflows/ci.yml:1-109 and consists of two parallel jobs: test and validate. Both jobs run on windows-latest runners and execute on pushes to main/master/develop branches, pull requests to these branches, or manual workflow dispatch.

Pipeline Objectives:

  • Verify that OfficeScrubNative.dll compiles successfully
  • Confirm the PowerShell module loads without errors
  • Validate module manifest and metadata
  • Ensure PowerShell Gallery publication requirements are met
  • Check for blocked files and security issues

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


Workflow Architecture

graph TB
    Trigger["Workflow Triggers:<br/>- push (main/master/develop)<br/>- pull_request<br/>- workflow_dispatch"]
    
    subgraph TestJob["Test Job (windows-latest)"]
        T1["Checkout Code<br/>actions/checkout@v3"]
        T2["Setup PowerShell<br/>Display $PSVersionTable"]
        T3["Build Native DLL<br/>build.ps1 -Clean"]
        T4["Test Module Import<br/>Import-Module OfficeScrubC2R.psd1"]
        T5["Test Module Functions<br/>Get-InstalledOfficeProducts"]
        T6["Validate Manifest<br/>Test-ModuleManifest"]
    end
    
    subgraph ValidateJob["Validate Job (windows-latest)"]
        V1["Checkout Code<br/>actions/checkout@v3"]
        V2["Build DLL<br/>build.ps1"]
        V3["Test Manifest<br/>Test-ModuleManifest"]
        V4["Check Required Files<br/>LICENSE, README.md, etc."]
        V5["Verify No Blocked Files<br/>Zone.Identifier check"]
    end
    
    Trigger --> TestJob
    Trigger --> ValidateJob
    
    T1 --> T2
    T2 --> T3
    T3 --> T4
    T4 --> T5
    T5 --> T6
    
    V1 --> V2
    V2 --> V3
    V3 --> V4
    V4 --> V5
Loading

The pipeline executes two jobs in parallel to maximize efficiency. Both jobs are independent and can fail individually, providing granular feedback on different aspects of module quality.

Sources: .github/workflows/ci.yml:3-8, .github/workflows/ci.yml:10-109


Test Job

The test job validates core module functionality through five sequential steps. It is defined in .github/workflows/ci.yml:11-63.

Workflow Steps

Step Purpose Command Exit Criteria
Checkout code Clone repository actions/checkout@v3 Repository files available
Setup PowerShell Display version info $PSVersionTable PowerShell version confirmed
Build native DLL Compile C# library .\build.ps1 -Clean OfficeScrubNative.dll exists
Test module import Load module Import-Module .\OfficeScrubC2R.psd1 -Force -Verbose Module loaded successfully
Test module functions Execute commands Get-Command -Module OfficeScrubC2R Functions exported
Validate manifest Check metadata Test-ModuleManifest -Path .\OfficeScrubC2R.psd1 Manifest valid

Build Verification

The build step .github/workflows/ci.yml:24-30 executes build.ps1 -Clean and verifies DLL creation:

.\build.ps1 -Clean
if (-not (Test-Path ".\OfficeScrubNative.dll")) {
  throw "DLL was not created"
}

This ensures the native layer compiles successfully before proceeding to module tests.

Module Import Test

The import test .github/workflows/ci.yml:32-36 verifies that the PowerShell module loads without errors and that the native DLL integration works:

Import-Module .\OfficeScrubC2R.psd1 -Force -Verbose
Get-Module OfficeScrubC2R

The -Verbose flag provides detailed loading information for troubleshooting.

Function Availability Test

The function test .github/workflows/ci.yml:38-54 verifies exported commands and attempts detection (which fails gracefully without admin rights):

$commands = Get-Command -Module OfficeScrubC2R
Write-Host "Available commands: $($commands.Name -join ', ')"

# Test detection only (safe to run in CI)
try {
  Get-InstalledOfficeProducts -ErrorAction Stop
}
catch {
  Write-Host "Expected error (no admin): $_"
}

The test intentionally catches the admin rights error, as CI runners don't have elevated privileges for Office manipulation.

Manifest Validation

The manifest validation step .github/workflows/ci.yml:56-62 extracts and displays key metadata:

$manifest = Test-ModuleManifest -Path .\OfficeScrubC2R.psd1
Write-Host "Module version: $($manifest.Version)"
Write-Host "Module name: $($manifest.Name)"
Write-Host "PowerShell version: $($manifest.PowerShellVersion)"

This confirms the manifest is well-formed and readable by PowerShell's module system.

Sources: .github/workflows/ci.yml:11-63


Validate Job

The validate job ensures PowerShell Gallery publication requirements are met. It is defined in .github/workflows/ci.yml:64-109.

sequenceDiagram
    participant GH as "GitHub Actions"
    participant Checkout as "actions/checkout@v3"
    participant Build as "build.ps1"
    participant Manifest as "Test-ModuleManifest"
    participant Files as "Required Files Check"
    participant Zone as "Zone.Identifier Check"
    
    GH->>Checkout: Clone repository
    Checkout->>Build: Execute build script
    Build->>Build: Compile OfficeScrubNative.dll
    Build->>Manifest: Validate manifest structure
    Manifest->>Manifest: Parse OfficeScrubC2R.psd1
    Manifest->>Files: Check required files
    Files->>Files: Verify: psd1, psm1, dll, LICENSE, README.md
    Files->>Zone: Scan for blocked files
    Zone->>Zone: Get-Item -Stream Zone.Identifier
    Zone->>GH: Report success/failure
Loading

Required Files Check

The required files check .github/workflows/ci.yml:84-99 ensures all necessary files for PowerShell Gallery publication are present:

$requiredFiles = @(
  'OfficeScrubC2R.psd1',
  'OfficeScrubC2R.psm1',
  'OfficeScrubNative.dll',
  'LICENSE',
  'README.md'
)

foreach ($file in $requiredFiles) {
  if (-not (Test-Path $file)) {
    throw "Required file missing: $file"
  }
}
File Purpose
OfficeScrubC2R.psd1 Module manifest with metadata
OfficeScrubC2R.psm1 Main PowerShell module
OfficeScrubNative.dll Native performance layer
LICENSE MIT license text
README.md Documentation and usage guide

Zone Identifier Check

The zone identifier check .github/workflows/ci.yml:101-108 scans all files for the alternate data stream Zone.Identifier, which Windows applies to downloaded files. This stream can cause PowerShell Gallery publication failures:

Get-ChildItem -Recurse -File | ForEach-Object {
  if ((Get-Item $_.FullName -Stream Zone.Identifier -ErrorAction SilentlyContinue)) {
    throw "Blocked file found: $($_.FullName)"
  }
}

Files with Zone.Identifier are considered "blocked" and must be unblocked using Unblock-File before publication.

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


Module Validation Script

The standalone validation script .github/scripts/Validate-Module.ps1:1-92 provides a comprehensive validation suite that can be run locally or in CI. It performs eight validation checks and generates a summary report.

Validation Checks

graph LR
    Start["Validate-Module.ps1"]
    
    subgraph Checks["Validation Checks"]
        C1["Manifest<br/>Test-ModuleManifest"]
        C2["DLL<br/>Test-Path OfficeScrubNative.dll"]
        C3["Import<br/>Import-Module"]
        C4["Commands<br/>Get-Command"]
        C5["Aliases<br/>Get-Alias"]
        C6["Required Files<br/>LICENSE, README.md, etc."]
        C7["Help<br/>Get-Help"]
    end
    
    Report["Results Report<br/>PASS/FAIL/SKIP"]
    Summary["Summary: X/Y Passed"]
    
    Start --> Checks
    C1 --> Report
    C2 --> Report
    C3 --> Report
    C4 --> Report
    C5 --> Report
    C6 --> Report
    C7 --> Report
    Report --> Summary
Loading

Check Implementation

Manifest Check .github/scripts/Validate-Module.ps1:6-13:

try {
    $manifest = Test-ModuleManifest -Path .\OfficeScrubC2R.psd1 -ErrorAction Stop
    $results['Manifest'] = "PASS (v$($manifest.Version))"
}
catch {
    $results['Manifest'] = "FAIL: $_"
}

DLL Check .github/scripts/Validate-Module.ps1:15-21:

if (Test-Path .\OfficeScrubNative.dll) {
    $results['DLL'] = "PASS"
}
else {
    $results['DLL'] = "FAIL: Not found"
}

Module Import Check .github/scripts/Validate-Module.ps1:23-40:

try {
    Import-Module .\OfficeScrubC2R.psd1 -Force -ErrorAction Stop
    $results['Import'] = "PASS"
    
    # Test commands
    $commands = Get-Command -Module OfficeScrubC2R
    $results['Commands'] = "PASS ($($commands.Count) exported)"
    
    # Test aliases
    $aliases = Get-Alias | Where-Object { $_.ModuleName -eq 'OfficeScrubC2R' }
    $results['Aliases'] = "PASS ($($aliases.Count) aliases)"
}
catch {
    $results['Import'] = "FAIL: $_"
    $results['Commands'] = "SKIP"
    $results['Aliases'] = "SKIP"
}

Required Files Check .github/scripts/Validate-Module.ps1:42-55:

$requiredFiles = @('LICENSE', 'README.md', 'CHANGELOG.md', 'CONTRIBUTING.md')
$missingFiles = @()
foreach ($file in $requiredFiles) {
    if (-not (Test-Path $file)) {
        $missingFiles += $file
    }
}
if ($missingFiles.Count -eq 0) {
    $results['Required Files'] = "PASS"
}
else {
    $results['Required Files'] = "FAIL: Missing $($missingFiles -join ', ')"
}

Help Documentation Check .github/scripts/Validate-Module.ps1:57-69:

try {
    $help = Get-Help Invoke-OfficeScrubC2R -ErrorAction Stop
    if ($help.Synopsis) {
        $results['Help'] = "PASS"
    }
    else {
        $results['Help'] = "FAIL: No synopsis"
    }
}
catch {
    $results['Help'] = "FAIL: $_"
}

Results Reporting

The script generates a color-coded report .github/scripts/Validate-Module.ps1:71-90:

$maxKeyLength = ($results.Keys | Measure-Object -Property Length -Maximum).Maximum
foreach ($key in $results.Keys | Sort-Object) {
    $value = $results[$key]
    $color = if ($value -like "PASS*") { 'Green' } 
             elseif ($value -like "FAIL*") { 'Red' } 
             else { 'Yellow' }
    Write-Host ("{0,-$($maxKeyLength + 2)}: {1}" -f $key, $value) -ForegroundColor $color
}

# Summary
$passed = ($results.Values | Where-Object { $_ -like "PASS*" }).Count
$total = $results.Count
Write-Host "`n=== Summary: $passed/$total checks passed ===" 

if ($passed -eq $total) {
    Write-Host "`nModule is ready for publication!" -ForegroundColor Green
}

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


PowerShell Gallery Compatibility

The CI pipeline enforces PowerShell Gallery publication requirements through multiple validation layers. These requirements ensure the module installs and functions correctly when downloaded via Install-Module.

Critical Requirements

Requirement Validation Location
Valid manifest Test-ModuleManifest must succeed .github/workflows/ci.yml:76-82
Required files present psd1, psm1, dll, LICENSE, README.md .github/workflows/ci.yml:84-99
No blocked files No Zone.Identifier streams .github/workflows/ci.yml:101-108
Import succeeds Import-Module without errors .github/workflows/ci.yml:32-36
Functions exported Get-Command -Module returns commands .github/workflows/ci.yml:38-54
Help available Get-Help returns synopsis .github/scripts/Validate-Module.ps1:57-69

Manifest Validation

The Test-ModuleManifest cmdlet validates:

  • Syntax: PowerShell data file structure
  • Required fields: ModuleVersion, GUID, Author, Description
  • File references: RootModule, RequiredAssemblies exist
  • Version constraints: PowerShellVersion, DotNetFrameworkVersion

Manifest validation runs in both jobs .github/workflows/ci.yml:56-62, .github/workflows/ci.yml:76-82 to catch issues early.

File Blocking Prevention

Windows applies Zone.Identifier alternate data streams to files downloaded from the internet. PowerShell Gallery requires all files to be unblocked. The CI pipeline fails if any file contains this stream .github/workflows/ci.yml:101-108.

Detection command:

Get-Item $_.FullName -Stream Zone.Identifier -ErrorAction SilentlyContinue

Remediation (local development):

Get-ChildItem -Recurse | Unblock-File

Sources: .github/workflows/ci.yml:64-109, .github/scripts/Validate-Module.ps1:1-92


CI/CD Integration with Build System

The CI pipeline integrates with the build system and module structure:

graph TB
    Source["Source Code:<br/>OfficeScrubC2R-Native.cs"]
    
    subgraph BuildSystem["Build System"]
        BuildScript["build.ps1<br/>Compilation Script"]
        CSC["csc.exe<br/>.NET Framework"]
        DotNet["dotnet build<br/>.NET Core"]
    end
    
    subgraph Artifacts["Build Artifacts"]
        DLL["OfficeScrubNative.dll"]
        CoreDLL["OfficeScrubNative.Core.dll"]
    end
    
    subgraph CIPipeline["CI Pipeline (ci.yml)"]
        TestJob["Test Job:<br/>Build + Import + Functions"]
        ValidateJob["Validate Job:<br/>Files + Manifest"]
    end
    
    subgraph ValidationScripts["Validation"]
        ValidateScript["Validate-Module.ps1<br/>Comprehensive Checks"]
        VerifyBuild["Verify-NativeBuild.ps1<br/>Build Verification"]
    end
    
    subgraph Distribution["Distribution"]
        Gallery["PowerShell Gallery<br/>Install-Module"]
        GitHub["GitHub Releases<br/>Manual Download"]
    end
    
    Source --> BuildScript
    BuildScript --> CSC
    BuildScript --> DotNet
    CSC --> DLL
    DotNet --> CoreDLL
    
    BuildScript --> CIPipeline
    DLL --> CIPipeline
    CoreDLL --> CIPipeline
    
    CIPipeline --> ValidationScripts
    ValidationScripts --> Distribution
Loading

Build Script Integration

Both CI jobs invoke build.ps1 to compile the native DLL:

The -Clean parameter forces a full rebuild by removing existing artifacts.

Validation Script Usage

The validation script can be invoked:

  1. Locally: Developers run before committing changes
  2. CI pipeline: Could be integrated as a dedicated step
  3. Pre-release: Final check before PowerShell Gallery publication

Manual execution:

.\.github\scripts\Validate-Module.ps1

Distribution Readiness

Successful CI runs indicate the module is ready for:

  • PowerShell Gallery publication via Publish-Module
  • GitHub Releases as downloadable ZIP archives
  • Local installation via Import-Module from filesystem

The validation job specifically ensures PowerShell Gallery requirements are met, preventing publication failures.

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


Workflow Execution Results

Success Criteria

The pipeline succeeds when:

  • Both jobs complete with exit code 0
  • OfficeScrubNative.dll compiles without errors
  • Module imports successfully in PowerShell
  • All exported functions are available
  • Manifest validation passes
  • All required files are present
  • No files contain Zone.Identifier streams

Failure Scenarios

Failure Job Cause Resolution
DLL not created test Compilation error in C# code Fix syntax errors in OfficeScrubC2R-Native.cs
Module import fails test Manifest error or DLL load failure Check manifest references and assembly loading
Missing required file validate File not committed to repository Add missing file and commit
Blocked file detected validate File has Zone.Identifier stream Run Unblock-File before committing
Manifest invalid test, validate Syntax error or missing required field Validate manifest locally with Test-ModuleManifest

Viewing Results

GitHub Actions provides:

  • Workflow run logs: Detailed output from each step
  • Job summaries: Pass/fail status for each job
  • Artifact downloads: Build outputs (if configured)
  • Email notifications: On workflow failure (if configured)

Navigate to the Actions tab in the GitHub repository to view workflow runs.

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


Running Validation Locally

Developers can run validation checks locally before pushing changes to catch issues early.

Manual Build and Validation

# 1. Build the native DLL
.\build.ps1 -Clean

# 2. Run comprehensive validation
.\.github\scripts\Validate-Module.ps1

# 3. Test module import
Import-Module .\OfficeScrubC2R.psd1 -Force -Verbose

# 4. Check for blocked files
Get-ChildItem -Recurse -File | ForEach-Object {
    if ((Get-Item $_.FullName -Stream Zone.Identifier -ErrorAction SilentlyContinue)) {
        Write-Warning "Blocked file: $($_.FullName)"
    }
}

# 5. Validate manifest
Test-ModuleManifest -Path .\OfficeScrubC2R.psd1

Pre-Commit Checklist

Before committing changes that affect module functionality:

  • Run .\build.ps1 -Clean successfully
  • Run .\.github\scripts\Validate-Module.ps1 with all PASS
  • Import module without errors
  • Test changed functionality
  • Update CHANGELOG.md if applicable
  • Verify no blocked files

This local validation prevents CI failures and accelerates the review process.

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

Clone this wiki locally