mbproxy: initial commit through Phase 9 (TxId multiplexing)
Adds the mbproxy service end-to-end. Phases 00-08 implement the production-ready single-listener / 1:1-backend transparent Modbus TCP proxy with bidirectional BCD rewriting for the ~54-PLC DL205/DL260 fleet. Phase 9 replaces the connection layer with a single backend socket per PLC plus MBAP TxId rewriting, lifting the H2-ECOM100's 4-concurrent-client cap as an operational ceiling. Phase 9 additions of note: - PlcMultiplexer + UpstreamPipe + TxIdAllocator + CorrelationMap - InFlightRequest with IReadOnlyList<InterestedParty> (load-bearing for Phase 10 read coalescing — do not collapse to a single field) - Per-request watchdog: surfaces Modbus exception 0x0B to upstream on BackendRequestTimeoutMs, defending against lost responses, dead-PLC paths, and pymodbus 3.13.0's concurrent-multiplexed- request bug (its ServerRequestHandler.last_pdu state race) - Status DTO + HTML gain inFlight / maxInFlight / txIdWraps / disconnectCascades / queueDepth (Tier 1.6 in docs/kpi.md) Tests: 263 unit + 38 E2E. Multiplexer correctness under truly concurrent backend traffic is proved against a stub backend in PlcMultiplexerTests; MultiplexerE2ETests paces requests so pymodbus 3.13's single-PDU framer stays in known-good mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
#Requires -RunAsAdministrator
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Removes the mbproxy Windows Service and its installed files.
|
||||
|
||||
.DESCRIPTION
|
||||
Stops the service, deletes the service registration, removes the binary
|
||||
install directory, and (unless -KeepConfig is specified) removes the data
|
||||
directory. Log files are always preserved: they are moved to a timestamped
|
||||
archive directory so post-uninstall diagnostics remain accessible.
|
||||
|
||||
.PARAMETER ServiceName
|
||||
Windows Service name to uninstall.
|
||||
Default: mbproxy
|
||||
|
||||
.PARAMETER InstallPath
|
||||
Directory that was used as the install target.
|
||||
Default: C:\Program Files\Mbproxy
|
||||
|
||||
.PARAMETER KeepConfig
|
||||
If specified, leaves %ProgramData%\mbproxy\appsettings.json in place.
|
||||
Logs are always preserved regardless of this flag.
|
||||
|
||||
.EXAMPLE
|
||||
.\uninstall.ps1
|
||||
|
||||
.EXAMPLE
|
||||
.\uninstall.ps1 -KeepConfig
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ServiceName = 'mbproxy',
|
||||
[string]$InstallPath = 'C:\Program Files\Mbproxy',
|
||||
[switch]$KeepConfig
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Write-Host "Uninstalling mbproxy service..." -ForegroundColor Cyan
|
||||
Write-Host " ServiceName : $ServiceName"
|
||||
Write-Host " InstallPath : $InstallPath"
|
||||
Write-Host " KeepConfig : $KeepConfig"
|
||||
|
||||
# ── 1. Stop the service ───────────────────────────────────────────────────────────────────
|
||||
|
||||
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($svc) {
|
||||
if ($svc.Status -eq 'Running') {
|
||||
Write-Host "Stopping service '$ServiceName'..."
|
||||
sc.exe stop $ServiceName | Out-Null
|
||||
|
||||
$deadline = [DateTime]::UtcNow.AddSeconds(30)
|
||||
do {
|
||||
Start-Sleep -Milliseconds 500
|
||||
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
} while ($svc -and $svc.Status -ne 'Stopped' -and [DateTime]::UtcNow -lt $deadline)
|
||||
|
||||
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($svc -and $svc.Status -ne 'Stopped') {
|
||||
Write-Warning "Service did not stop within 30 s — attempting force delete."
|
||||
}
|
||||
}
|
||||
|
||||
# ── 2. Delete the service ─────────────────────────────────────────────────────────────
|
||||
Write-Host "Deleting service registration '$ServiceName'..."
|
||||
sc.exe delete $ServiceName | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning "sc.exe delete returned $LASTEXITCODE — the service entry may already be gone."
|
||||
}
|
||||
} else {
|
||||
Write-Host "Service '$ServiceName' not found — skipping stop/delete."
|
||||
}
|
||||
|
||||
# ── 3. Archive log files ─────────────────────────────────────────────────────────────────
|
||||
# Logs are ALWAYS archived (never deleted) so post-uninstall crash diagnostics survive.
|
||||
|
||||
$dataDir = Join-Path $env:ProgramData 'mbproxy'
|
||||
$logDir = Join-Path $dataDir 'logs'
|
||||
|
||||
if (Test-Path $logDir) {
|
||||
$timestamp = [DateTime]::UtcNow.ToString('yyyyMMddTHHmmssZ')
|
||||
$archiveName = "mbproxy.archived-$timestamp"
|
||||
$archiveRoot = Join-Path $env:ProgramData $archiveName
|
||||
$archiveLogs = Join-Path $archiveRoot 'logs'
|
||||
|
||||
Write-Host "Archiving logs to '$archiveLogs'..."
|
||||
New-Item -ItemType Directory -Force $archiveLogs | Out-Null
|
||||
Get-ChildItem -Path $logDir | ForEach-Object {
|
||||
Move-Item -Path $_.FullName -Destination $archiveLogs -Force
|
||||
}
|
||||
Write-Host " Logs archived to: $archiveLogs" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ── 4. Remove data directory ─────────────────────────────────────────────────────────────
|
||||
|
||||
if (Test-Path $dataDir) {
|
||||
if ($KeepConfig) {
|
||||
# Remove everything except appsettings.json; then remove the now-empty log dir.
|
||||
Write-Host "Keeping config at '$dataDir\appsettings.json' (-KeepConfig specified)."
|
||||
$logDirPath = Join-Path $dataDir 'logs'
|
||||
if (Test-Path $logDirPath) {
|
||||
Remove-Item -Recurse -Force $logDirPath -ErrorAction SilentlyContinue
|
||||
}
|
||||
} else {
|
||||
Write-Host "Removing data directory '$dataDir'..."
|
||||
Remove-Item -Recurse -Force $dataDir -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# ── 5. Remove binary install directory ───────────────────────────────────────────────────
|
||||
|
||||
if (Test-Path $InstallPath) {
|
||||
Write-Host "Removing install directory '$InstallPath'..."
|
||||
Remove-Item -Recurse -Force $InstallPath -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
Write-Host "Install directory '$InstallPath' not found — skipping."
|
||||
}
|
||||
|
||||
# ── 6. Remove Windows Event Log source ───────────────────────────────────────────────────
|
||||
|
||||
if ([System.Diagnostics.EventLog]::SourceExists('mbproxy')) {
|
||||
Write-Host "Removing Windows Event Log source 'mbproxy'..."
|
||||
try {
|
||||
Remove-EventLog -Source 'mbproxy'
|
||||
} catch {
|
||||
Write-Warning "Could not remove Event Log source: $_"
|
||||
}
|
||||
} else {
|
||||
Write-Host "Windows Event Log source 'mbproxy' not registered — skipping."
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Uninstall complete." -ForegroundColor Green
|
||||
|
||||
if (Test-Path (Join-Path $env:ProgramData 'mbproxy.archived-*')) {
|
||||
Write-Host "Archived logs can be found under: $env:ProgramData\mbproxy.archived-*" -ForegroundColor Yellow
|
||||
}
|
||||
Reference in New Issue
Block a user