Deployment Verification and Progressive Rollout with Pester

PesterForge · July 2026 · 7 min read

📦 Run it yourself — the companion repository reserves separate Deployment/ and PostDeploy/ suites so installation and operational verification do not blur together.

A deployment tool says success.

That proves the deployment tool did not report failure.

It does not automatically prove that:

Deployment verification begins where the installer stops.

What you will learn

The running deployment example

The Partner Feed Guardian module runs on several internal automation runners.

A release pipeline copies version 0.4.0 to the first canary runner before continuing to the rest of the fleet.

The rollout has three distinct questions:

  1. Did the deployment place the intended artifact on the target?
  2. Does the target load and expose the intended module?
  3. Can the target complete a controlled workload?

Those questions deserve separate tests.

Deployment tests: inspect the artifact

Deployment tests verify installation facts.

Describe 'PartnerFeedGuardian deployment' `
    -Tag 'Gate.Deployment', 'Environment.Canary', 'Risk.ReadOnly' {

    It 'installs the approved module version' {
        $module = Get-Module -ListAvailable PartnerFeedGuardian |
            Where-Object Version -eq ([version]'0.4.0') |
            Select-Object -First 1

        $module | Should-NotBeNull
    }

    It 'matches the approved module hash' {
        $module = Get-Module -ListAvailable PartnerFeedGuardian |
            Where-Object Version -eq ([version]'0.4.0') |
            Select-Object -First 1

        $hash = Get-FileHash `
            -Path (Join-Path $module.ModuleBase 'PartnerFeedGuardian.psm1') `
            -Algorithm SHA256

        $hash.Hash | Should-BeString $approvedHash
    }
}

The approved hash must come from the release artifact metadata, not from recalculating the deployed file and then calling whatever appears “approved.”

Test module-path precedence

Multiple versions can coexist. The right version may be present but not selected.

It 'resolves the approved version first' {
    Remove-Module PartnerFeedGuardian -ErrorAction SilentlyContinue

    Import-Module PartnerFeedGuardian -Force
    $loaded = Get-Module PartnerFeedGuardian

    $loaded.Version.ToString() | Should-BeString '0.4.0'
    $loaded.ModuleBase | Should-BeString $expectedModuleBase
}

This proves what a fresh session loads by name on the target.

For long-running processes, also test the process lifecycle. Copying a new module does not replace code already loaded into an existing runspace.

Verify the exported surface

A release can accidentally export private helpers or omit a public command.

It 'exports only the approved commands' {
    $expected = @(
        'Compare-PfgRoutingConfiguration'
        'Invoke-PfgBatch'
        'New-PfgBatch'
        'New-PfgBatchPlan'
        'Test-PfgBatchEligibility'
        'Test-PfgPreflight'
    )

    $actual = Get-Command -Module PartnerFeedGuardian |
        Where-Object CommandType -eq Function |
        Select-Object -ExpandProperty Name |
        Sort-Object

    $actual | Should-BeCollection ($expected | Sort-Object)
}

In the companion sample, more adapter functions are exported for teaching. A production module should usually export only the intended public surface.

Do not test the source directory

This is a subtle and common deployment-test mistake:

Import-Module "$PSScriptRoot/../src/PartnerFeedGuardian.psd1"

That tests the repository copy, not the deployed module.

Deployment verification must import from the target’s real module path or exact deployed location.

The test itself can be delivered separately, but the command under test must come from the deployed artifact.

Run under the production identity

A canary test run under an administrator proves less than one run under the automation account.

The deployment suite should execute through the same host model as production:

pwsh -NoLogo -NoProfile -NonInteractive -File Invoke-CanaryGate.ps1

Use the same:

The canary is valuable because it reveals environmental differences before the release reaches every runner.

Post-deployment tests: exercise controlled behavior

After artifact verification, run a small workload.

For the Partner Feed Guardian, use a synthetic lab or canary batch that cannot enter business reporting.

Describe 'PartnerFeedGuardian canary workload' `
    -Tag 'Gate.PostDeploy', 'Environment.Canary' {

    It 'rejects an unsupported canary schema with the deployed module' {
        $batch = New-PfgBatch `
            -PartnerId 'PFG-CANARY' `
            -BatchId "CANARY-$([guid]::NewGuid().Guid)" `
            -SchemaVersion 'unsupported-canary' `
            -EffectiveDate (Get-Date) `
            -Payloads @('orders.json') `
            -ManifestPayloads @('orders.json')

        $result = Test-PfgBatchEligibility `
            -Batch $batch `
            -SupportedSchemas @('2.1')

        $result.Status | Should-Be 'Rejected'
        $result.Reasons |
            Should-ContainCollection @('UnsupportedSchema')
    }
}

This is still read-only decision behavior. A later production-probe test can exercise the full synthetic path with stronger safeguards.

Separate the suites

Use separate folders and tags:

tests/
├── Deployment/
└── PostDeploy/

Deployment tests answer:

Is the intended artifact installed and selected?

Post-deployment tests answer:

Can the deployed system perform the controlled behavior we require?

If they are mixed into one large file, failure diagnosis becomes slower and rollback policy less precise.

Progressive rollout is a sequence of gates

A practical rollout might be:

  1. Deploy to one canary runner.
  2. Run deployment verification.
  3. Run a controlled workload.
  4. Wait through a defined observation period.
  5. Continue to a small runner group.
  6. Repeat verification.
  7. Continue to the remaining fleet.

Pester can provide evidence at each stage. The deployment orchestrator controls the progression.

$result = Invoke-Pester -Configuration $postDeployConfig
Assert-PfgGateResult -Result $result -GateName 'Canary post-deployment'

# Only after this returns successfully should the orchestrator continue.
Start-PfgNextRolloutWave

Pester should not quietly start the next wave from inside an It block. Keep test evaluation and deployment orchestration separate.

Define stop conditions before rollout

Examples:

Do not decide the policy emotionally while production is changing.

Rollback must be verified too

The prior package should be available before rollout.

After rollback, run a dedicated verification:

It 'loads the restored version after rollback' {
    Remove-Module PartnerFeedGuardian -ErrorAction SilentlyContinue
    Import-Module PartnerFeedGuardian -RequiredVersion 0.3.2

    (Get-Module PartnerFeedGuardian).Version.ToString() |
        Should-BeString '0.3.2'
}

Then run the prior version’s known-good controlled workload.

Rollback is not complete when the old files are copied. It is complete when the restored system is verified.

Watch for version entanglement

During a progressive rollout, different runners may intentionally run different versions.

A test must know which version is expected on the target it is evaluating. Comparing every runner to “latest” can incorrectly flag the canary process or, worse, approve a target against the wrong expectation.

Pass release metadata explicitly:

param(
    [Parameter(Mandatory)]
    [version]$ExpectedVersion,

    [Parameter(Mandatory)]
    [string]$ExpectedWave
)

Store those values with the result.

What these tests prove

Deployment tests can prove that the selected target contains, resolves, and loads the approved artifact under the intended execution context.

Post-deployment tests can prove that the deployed artifact completes selected controlled behavior.

What they do not prove

They do not prove every production dependency is healthy, every partner path works, or the system will remain healthy under full load. They support a controlled rollout decision; they do not replace observation and monitoring.

Try it yourself

For one PowerShell module deployment, create tests for:

  1. Exact version
  2. Artifact hash or signature
  3. Module-path precedence
  4. Exported commands
  5. Actual loaded version in a fresh production-like session
  6. One controlled workload
  7. Restored version after rollback

Run them first on one canary target.

Common mistakes

Testing the repository copy instead of the deployed artifact. Import from the real target path.

Checking only that the new version exists. Prove that a fresh session selects and loads it.

Running canary tests as an administrator. Use the production execution identity.

Combining installation and behavior tests. Separate suites make failures and rollback decisions clearer.

Letting Pester orchestrate the rollout. Pester evaluates; the deployment system advances or stops.

Recap

A deployment command’s success is only the beginning of deployment evidence.

Verify the artifact, module-path resolution, loaded version, public surface, execution identity, and controlled behavior. Roll out progressively and define stop conditions before the first canary changes.

Next up: Part 8 — Safe Production Tests and Synthetic Transactions. We will move from canary validation to carefully controlled tests that interact with production through the same path real work uses.


← All posts