This plan outlines the migration of your PowerShell-based deployment process (see Deployment.psm1) remote-execution model (PowerShell WinRM / C# WMI) to a high-performance, asynchronous Client-Server Architecture using .NET 10, Minimal APIs, and .NET Channels.
Deployment Automation: Architectural Refactoring Plan#
1. Executive Summary#
The goal is to replace the current PowerShell remote-execution model with a specialized Deployment Agent (Server API) and a Deployment Orchestrator (Client).
By using .NET Channels, the Server API handles heavy operations (file extraction, IIS configuration) in the background, preventing HTTP timeouts. By moving SQL Logic to the Client, we ensure that database registration only happens if the local server operations succeed, providing a "Two-Phase Commit" feel to the deployment.
2. Architecture Diagram#
graph TD
subgraph Client [Deployment Orchestrator - Admin PC / CI/CD]
CO[Orchestrator Logic]
Dapper[(Dapper / SQL Client)]
end
subgraph Server [Deployment Agent - Target VM]
direction TB
API[Minimal API Endpoint]
Chan[[.NET Channel - In-Memory Queue]]
BGS[Background Worker Service]
subgraph LocalTasks [Local System Operations]
IIS[Microsoft.Web.Administration]
SVC[System.ServiceProcess]
FS[System.IO.Compression]
end
end
subgraph DB [Database Server]
SQL[(CommandCenter DB)]
end
%% Flow
CO -->|1. Pre-Check| SQL
CO -->|2. POST Zip + JSON| API
API -->|3. Queue Job| Chan
Chan -->|4. Process| BGS
BGS -->|5. Manage| LocalTasks
CO -->|6. Poll Status| API
CO -->|7. Finalize/Activate| SQL
3. Technology Stack & Requirements#
| Component | Technology | Purpose |
|---|---|---|
| Runtime | .NET 10 (Windows TFM) | Core framework (requires net10.0-windows) |
| API | ASP.NET Core Minimal APIs | Lightweight entry point for deployment requests |
| Backgrounding | System.Threading.Channels |
Thread-safe, in-memory producer/consumer queue |
| IIS Management | Microsoft.Web.Administration |
Local IIS AppPool, Site, and WebApp management |
| Database | Dapper & Microsoft.Data.SqlClient |
Fast SQL execution for CommandCenter registration |
| Service Mgmt | System.ServiceProcess.ServiceController |
Controlling and installing Windows Services |
4. Server-Side Implementation (The Agent)#
The Server API acts as a "Local Agent." It receives the deployment package and immediately returns a 202 Accepted status while processing the task in the background.
A. The Deployment Task & Channel#
The API uses a Channel<T> to pass data between the HTTP thread and the Background Service.
public record DeploymentJob(
Guid JobId,
DeploymentPayload Config,
string TempPackagePath);
public record DeploymentPayload(
string Environment,
string Version,
string Package,
string FormattedAppVersion);
B. Minimal API Implementation#
The API receives the multipart/form-data request, saves the zip to a temporary location, and queues a job.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(Channel.CreateUnbounded<DeploymentJob>());
builder.Services.AddSingleton<JobStatusStore>(); // Custom Dictionary wrapper for status tracking
builder.Services.AddHostedService<DeploymentWorker>();
var app = builder.Build();
app.MapPost("/deploy", async (IFormFile file, [FromForm] string json, Channel<DeploymentJob> channel, JobStatusStore status) => {
var config = JsonSerializer.Deserialize<DeploymentPayload>(json);
var jobId = Guid.NewGuid();
// 1. Save file locally
var tempPath = Path.Combine(Path.GetTempPath(), $"{jobId}.zip");
using (var fs = new FileStream(tempPath, FileMode.Create)) await file.CopyToAsync(fs);
// 2. Queue Job
await channel.Writer.WriteAsync(new DeploymentJob(jobId, config, tempPath));
status.Update(jobId, "Queued");
return Results.Accepted($"/status/{jobId}", new { JobId = jobId });
});
app.MapGet("/status/{id}", (Guid id, JobStatusStore status) => Results.Ok(status.Get(id)));
C. Background Worker (The Heavy Lifter)#
This service consumes the channel and performs the local PowerShell-equivalent tasks.
public class DeploymentWorker : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
await foreach (var job in _channel.Reader.ReadAllAsync(stoppingToken)) {
try {
// Logic based on your PowerShell 'Install-BenefitManager'
// 1. Generate TargetPath locally (e.g. D:\Deployments\Productie\25.5.29)
// 2. ZipFile.ExtractToDirectory(job.TempPackagePath, targetPath);
// 3. ConfigureIIS(job.Config, targetPath);
// 4. InstallWindowsService(job.Config, targetPath);
_statusStore.Update(job.JobId, "Completed");
} catch (Exception ex) {
_statusStore.Update(job.JobId, $"Failed: {ex.Message}");
}
}
}
}
5. Client-Side Implementation (The Orchestrator)#
The client performs the high-level orchestration, ensuring the database is updated only when the server is ready.
A. Phase 1: Pre-Check (SQL)#
Using Dapper, check if the version is already active or if the environment is ready.
using var conn = new SqlConnection(connectionString);
var isDeployed = await conn.ExecuteScalarAsync<bool>(
"SELECT COUNT(1) FROM TblApplicationVersion WHERE Identifier = @Version",
new { config.Version });
B. Phase 2: Trigger Agent (HTTP)#
Send the ZIP file and the JSON configuration to the Server API.
C. Phase 3: Poll for Completion#
The client loops (with a timeout) calling the /status/{id} endpoint until it receives "Completed".
D. Phase 4: Finalize Deployment (SQL)#
Only after the Server API confirms the files are extracted and IIS is configured, the client runs the MERGE SQL logic to set Online = 1 and Active = 1.
6. Implementation Checklist#
- Preparation:
- Define a standard base path on all servers (e.g.,
D:\BenefitManager\Deployments). - Create a "Local Agent" Windows Service project for the Server API.
- Define a standard base path on all servers (e.g.,
- Server Agent Development:
- Implement
Microsoft.Web.Administrationlogic to replaceNew-IISApplication. - Implement
System.ServiceProcesslogic to replaceInstall-BenefitManagerService. - Secure the API with an API Key or Windows Authentication.
- Implement
- Client Development:
- Port the PowerShell SQL scripts into C# string constants or embedded resources.
- Implement the polling logic with
System.Net.Http.
- Testing:
- Verify that
Uninstallcorrectly stops services and removes IIS Applications without deleting shared AppPools. - Test "Rollback" by having the client point the SQL
VirtualDirectoryback to the previous version if the Agent fails.
- Verify that
7. Data Sample: Full Payload#
The client sends this JSON to the API inside the multipart/form-data request:
{
"Environment": "Productie",
"Build_DefinitionName": "Azure Deployment",
"Package": "BenefitManager_25.5.29.zip",
"Version": "25.5.29",
"Id": "48270",
"FormattedAppVersion": "Productie_25.5"
}
Environment and Version to calculate the local TargetPath automatically, ensuring consistency across the fleet.
8. Summary of Benefits#
- Security: No need to open WinRM/WMI ports across environments. The API handles its own local security context.
- Reliability: Uploading a zip file over HTTP is much faster and less prone to network drops than copying files over UNC paths (
\\server\c$\...). - Maintainability: The C# code is strongly typed. The API can easily be extended with Swagger/OpenAPI to test deployments manually.
- Clean Uninstall: The API inherently knows where things are installed locally, making the
Uninstallprocess (stopping services, removing IIS bindings, deleting directories) safe and contained.