Deployment Verification and Progressive Rollout with Pester
📦 Run it yourself — the companion repository reserves separate
Deployment/andPostDeploy/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:
- The expected package landed
- The signature is valid
- The new module wins module-path resolution
- The process loaded the new version
- The production identity can call it
- The configuration matches the release
- The basic workload completes
Deployment verification begins where the installer stops.
What you will learn
- Why installation and post-deployment tests should be separate
- What to verify for a PowerShell module rollout
- How to use a canary runner
- How to define stop and rollback conditions
- How to avoid testing the source tree instead of the deployed artifact
- How to prove the running session loaded the intended version
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:
- Did the deployment place the intended artifact on the target?
- Does the target load and expose the intended module?
- 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:
- Account
- PowerShell edition
- Bitness
- Module path
- Working directory policy
- Secret provider
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:
- Deploy to one canary runner.
- Run deployment verification.
- Run a controlled workload.
- Wait through a defined observation period.
- Continue to a small runner group.
- Repeat verification.
- 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-PfgNextRolloutWavePester 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:
- Wrong module version: stop and roll back canary
- Hash mismatch: stop immediately; investigate artifact integrity
- Command missing: stop and roll back
- Controlled workload fails: stop; preserve evidence
- Test dependency unavailable: stop; do not promote without evidence
- Noncritical reporting test fails: stop or warn according to preapproved policy
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:
- Exact version
- Artifact hash or signature
- Module-path precedence
- Exported commands
- Actual loaded version in a fresh production-like session
- One controlled workload
- 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.