9.2 KiB
ESP — PowerShell Script Architecture
- Overview
- Entry Point — How to Call the Scripts
- Tier 1 — Entry Point (Presentation Layer)
- Tier 2 — Orchestration Layer (Domain Layer)
- Tier 3 — Functional Layer (Workers)
- Helper Modules
- Testing Strategy
- Incremental Automation Plan
- Architecture Diagram (Text)
Overview
The deployment scripts are structured in three tiers, loosely analogous to a presentation/domain/data layered architecture in software. This separation exists to:
- Keep ESP-specific knowledge isolated to higher tiers
- Allow Tier 3 functions to be tested in isolation
- Allow the scripts to run independently of AzDO
- Make incremental automation easier (replace manual prompts one function at a time)
Entry Point — How to Call the Scripts
Invoke-Deployment.ps1 -Customer ROMAC -Environment DEV -Stages PREDEPLOY,DEPLOY,POSTDEPLOY
You can pass any combination of stages. For example, to only validate:
Invoke-Deployment.ps1 -Customer ROMAC -Environment DEV -Stages VALIDATE
Tier 1 — Entry Point (Presentation Layer)
Responsibility
- The single entry point to the whole deployment system
- Loads and validates the manifest (see ESP — The Manifest)
- Loads all required PowerShell modules (Tier 2, Tier 3, helpers)
- Calls the appropriate Tier 2 functions for the requested stages
- Passes the manifest object through to Tier 2
Key Characteristics
- One script:
Invoke-Deployment.ps1 - Does NOT contain deployment logic itself
- Acts as a wiring layer only
Pseudocode
param(
[string]$Customer,
[string]$Environment,
[string[]]$Stages
)
$manifest = Load-Manifest -Customer $Customer -Environment $Environment
Assert-ManifestValid -Manifest $manifest
Import-Module ./Tier2/PreDeployment.psm1
Import-Module ./Tier2/Deployment.psm1
Import-Module ./Tier2/PostDeployment.psm1
Import-Module ./Helpers/Logging.psm1
foreach ($stage in $Stages) {
switch ($stage) {
"PREDEPLOY" { Invoke-PreDeployment -Manifest $manifest }
"DEPLOY" { Invoke-Deployment -Manifest $manifest }
"POSTDEPLOY" { Invoke-PostDeployment -Manifest $manifest }
}
}
Tier 2 — Orchestration Layer (Domain Layer)
Responsibility
- One script per deployment stage (e.g.
PreDeployment.psm1,Deployment.psm1) - Contains the logic and sequencing of what needs to happen
- Decides which Tier 3 functions to call and in what order
- Reacts to return values from Tier 3 (e.g. if a function fails, abort or retry)
Key Constraints
- Does NOT pass the manifest to Tier 3 — all required data is extracted and passed as explicit parameters
- Does NOT directly perform any action itself — delegates entirely to Tier 3
- Does have knowledge of ESP and what a deployment involves
Why "Does Not Pass Manifest to Tier 3"?
Tier 3 functions are designed to be generic and reusable. If they accepted a manifest object, they'd need to know about its structure — breaking isolation. Instead, Tier 2 extracts what Tier 3 needs:
# BAD — passes the whole manifest (couples Tier 3 to manifest structure)
Stop-WindowsService -Manifest $manifest
# GOOD — extracts what's needed and passes explicitly
Stop-WindowsService -ServiceName $manifest.Services.DLService.Name `
-ServerName $manifest.Servers.AppServer
Scripts in This Tier
| Script | Stage it covers |
|---|---|
Validation.psm1 |
VALIDATE stage |
Prerequisites.psm1 |
PREREQUISITES stage |
PreDeployment.psm1 |
PREDEPLOY stage |
Deployment.psm1 |
DEPLOY stage |
PostDeployment.psm1 |
POSTDEPLOY stage |
Tier 3 — Functional Layer (Workers)
Responsibility
- Small, single-purpose functions
- No knowledge of ESP, the manifest, or deployment context
- Accept all required data as explicit parameters
- Can be tested in isolation with mock data
Key Characteristics
- Examples:
Stop-WindowsService,Invoke-SqlScript,Copy-Files,Get-RemoteSession,Send-CitrixNotification - Initially implemented as manual prompt wrappers — the function prints instructions to a human and waits for confirmation
- Will be replaced with real automation incrementally
The Manual Prompt Pattern (Current State)
function Stop-WindowsService {
param([string]$ServiceName, [string]$ServerName)
# Current implementation — prompts a human
$response = Invoke-ManualPrompt -Message "Please stop service '$ServiceName' on '$ServerName', then press Enter."
return $response
}
Target Implementation (Automated)
function Stop-WindowsService {
param([string]$ServiceName, [string]$ServerName)
$session = Get-RemoteSession -ServerName $ServerName
Invoke-Command -Session $session -ScriptBlock {
Stop-Service -Name $using:ServiceName -Force
(Get-Service -Name $using:ServiceName).WaitForStatus('Stopped', '00:01:00')
}
}
Exceptions — Functions That Cannot Be Manual Prompts
A small number of Tier 3 functions must be implemented for real from the start because they return data the scripts need to function:
| Function | Why it can't be a manual prompt |
|---|---|
Load-Manifest |
Returns the manifest object — must actually read file |
Get-RemoteSession |
Returns a PS session — must actually connect |
Read-File |
Returns file contents — must actually read |
Helper Modules
In addition to the three tiers, general-purpose helper modules exist that can be called from anywhere (Tier 1, 2, or 3):
| Module | Purpose |
|---|---|
Logging.psm1 |
Write structured log output |
Prompt.psm1 |
The manual prompt mechanism used by Tier 3 |
Helpers follow the same rule as Tier 3: the manifest is never passed to them.
Testing Strategy
Because Tier 3 functions are isolated, they can be unit tested without any connection to a real ESP environment:
# Example Pester test for Stop-WindowsService
Describe "Stop-WindowsService" {
It "calls Invoke-Command with correct service name" {
Mock Invoke-Command {}
Mock Get-RemoteSession { return [PSCustomObject]@{ Session = "MockSession" } }
Stop-WindowsService -ServiceName "DLService" -ServerName "SERVER01"
Assert-MockCalled Invoke-Command -Times 1
}
}
Incremental Automation Plan
The architecture is designed so that automation can be added one function at a time, without restructuring anything:
- Deploy with all Tier 3 functions as manual prompts (guided checklist)
- Identify the lowest-risk, simplest functions to automate first
- Replace manual prompts with real implementations one by one
- Each replacement can be independently tested before deployment
Suggested automation order (rough):
Stop-WindowsService/Start-WindowsService— well-understood, low riskCopy-Files— straightforward file operationsInvoke-SqlScript— once databases are in dacpac-ready state- Citrix-related functions — last, most complex
Architecture Diagram (Text)
AzDO Pipeline (YAML)
│
└─► Invoke-Deployment.ps1 [TIER 1]
│ Loads manifest
│ Loads all modules
│
├─► PreDeployment.psm1 [TIER 2]
│ ├─► Copy-Files [TIER 3]
│ └─► ...
│
├─► Deployment.psm1 [TIER 2]
│ ├─► Stop-WindowsService [TIER 3]
│ ├─► Invoke-SqlScript [TIER 3]
│ ├─► Start-WindowsService [TIER 3]
│ └─► ...
│
└─► PostDeployment.psm1 [TIER 2]
├─► Get-ServiceStatus [TIER 3]
└─► ...
[Helpers: Logging, Prompt — available at any tier]
Mermaid Diagram (Text)
graph TD
%% Tier 1
subgraph "Tier 1 - Entry Point"
A[Invoke-Deployment.ps1]
end
%% Tier 2
subgraph "Tier 2 - Deployment Phases"
B[PreDeployment.psm1]
C[Deployment.psm1]
D[PostDeployment.psm1]
end
%% Tier 3
subgraph "Tier 3 - Actions"
E[Copy-Files]
F[Stop-WindowsService]
G[Invoke-SqlScript]
H[Start-WindowsService]
I[Get-ServiceStatus]
end
%% Helpers
subgraph "Helpers (All Tiers)"
J[Logging Helper]
K[Prompt Helper]
end
%% Relationships
A --> B
A --> C
A --> D
B --> E
C --> F
C --> G
C --> H
D --> I
A --> J
A --> K