ce25581596
Replace OTOPCUA_HISTORIAN_PIPE/OTOPCUA_ALLOWED_SID with TCP transport env (OTOPCUA_HISTORIAN_TCP_PORT, OTOPCUA_HISTORIAN_BIND, OTOPCUA_HISTORIAN_TLS_ENABLED, OTOPCUA_HISTORIAN_TLS_CERT/PASSWORD) in Install-Services.ps1; add idempotent Windows Firewall inbound rule for the TCP port. Add new params for all TCP/TLS options with cert provisioning guidance. Update Refresh-Services.ps1 Step 4b comment (PipeServer → TcpFrameServer) and add a Step 5 note clarifying that TCP/TLS env is set at install time, not on refresh.
253 lines
11 KiB
PowerShell
253 lines
11 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$RepoRoot = "C:\Users\dohertj2\Desktop\lmxopcua",
|
|
[string]$GatewayRoot = "C:\Users\dohertj2\Desktop\mxaccessgw",
|
|
[string]$PublishRoot = "C:\publish",
|
|
[switch]$SkipBackup,
|
|
[switch]$WhatIf
|
|
)
|
|
|
|
# PR D.1 — refresh C:\publish + restart services for the alarms-over-gateway
|
|
# epic. Stops services in reverse-dependency order (OtOpcUa →
|
|
# OtOpcUaWonderwareHistorian → MxAccessGw), refreshes binaries from the
|
|
# repos, then starts in forward order. A timestamped backup of the existing
|
|
# C:\publish trees lands under C:\publish\.backup-YYYY-MM-DD\ unless
|
|
# -SkipBackup is supplied.
|
|
#
|
|
# Designed to run as a single elevated PowerShell session on the deploy host
|
|
# (the dev rig today; production refresh is a separate runbook).
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Step([string]$Message) {
|
|
Write-Host ""
|
|
Write-Host "==> $Message" -ForegroundColor Cyan
|
|
}
|
|
|
|
function Run([scriptblock]$Block, [string]$Description) {
|
|
if ($WhatIf) {
|
|
Write-Host " (skip) $Description" -ForegroundColor DarkYellow
|
|
return
|
|
}
|
|
Write-Host " $Description"
|
|
& $Block
|
|
}
|
|
|
|
function Test-NssmService([string]$Name) {
|
|
$svc = Get-Service -Name $Name -ErrorAction SilentlyContinue
|
|
return $null -ne $svc
|
|
}
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 1: Stop in reverse dependency order
|
|
# ------------------------------------------------------------------------
|
|
|
|
Step "Stopping services (OtOpcUaHost > OtOpcUaWonderwareHistorian > MxAccessGw)"
|
|
|
|
foreach ($name in @('OtOpcUaHost', 'OtOpcUaWonderwareHistorian', 'MxAccessGw')) {
|
|
if (Test-NssmService $name) {
|
|
Run { Stop-Service $name -Force -ErrorAction SilentlyContinue } "stop $name"
|
|
}
|
|
else {
|
|
Write-Host " ($name not installed; skipping)" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
|
|
if (-not $WhatIf) {
|
|
Start-Sleep -Seconds 3
|
|
Get-Process MxGateway.Server, MxGateway.Worker, OtOpcUa.Host, OtOpcUa.Driver.Historian.Wonderware -ErrorAction SilentlyContinue |
|
|
ForEach-Object {
|
|
Write-Host " killing residual process $($_.ProcessName) (PID=$($_.Id))" -ForegroundColor DarkYellow
|
|
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 2: Backup existing C:\publish trees
|
|
# ------------------------------------------------------------------------
|
|
|
|
if (-not $SkipBackup -and (Test-Path $PublishRoot)) {
|
|
$backupRoot = Join-Path $PublishRoot ".backup-$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))"
|
|
Step "Backing up $PublishRoot → $backupRoot"
|
|
|
|
Run {
|
|
New-Item -ItemType Directory -Path $backupRoot | Out-Null
|
|
foreach ($subdir in @('mxaccessgw', 'lmxopcua')) {
|
|
$src = Join-Path $PublishRoot $subdir
|
|
if (Test-Path $src) {
|
|
Copy-Item -Recurse -Path $src -Destination (Join-Path $backupRoot $subdir)
|
|
}
|
|
}
|
|
} "snapshot publish dirs (rollback target)"
|
|
}
|
|
else {
|
|
Write-Host " (backup skipped)" -ForegroundColor DarkGray
|
|
}
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 3: Refresh mxaccessgw binaries (Track A output)
|
|
# ------------------------------------------------------------------------
|
|
|
|
Step "Building + copying mxaccessgw binaries from $GatewayRoot"
|
|
|
|
Run {
|
|
& dotnet build "$GatewayRoot\src\MxGateway.Worker" -c Release | Out-Null
|
|
& dotnet build "$GatewayRoot\src\MxGateway.Server" -c Release | Out-Null
|
|
} "dotnet build (Worker x86 net48 + Server net10.0)"
|
|
|
|
Run {
|
|
$serverDest = Join-Path $PublishRoot "mxaccessgw\Server"
|
|
$workerDest = Join-Path $PublishRoot "mxaccessgw\Worker"
|
|
if (-not (Test-Path $serverDest)) { New-Item -ItemType Directory -Path $serverDest -Force | Out-Null }
|
|
if (-not (Test-Path $workerDest)) { New-Item -ItemType Directory -Path $workerDest -Force | Out-Null }
|
|
Copy-Item -Recurse -Force "$GatewayRoot\src\MxGateway.Server\bin\Release\net10.0\*" $serverDest
|
|
Copy-Item -Recurse -Force "$GatewayRoot\src\MxGateway.Worker\bin\x86\Release\net48\*" $workerDest
|
|
} "copy gateway server + worker outputs"
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 4: Refresh OtOpcUa + Wonderware historian sidecar
|
|
# ------------------------------------------------------------------------
|
|
|
|
Step "Publishing OtOpcUa.Host + Wonderware historian sidecar from $RepoRoot"
|
|
|
|
Run {
|
|
& dotnet publish "$RepoRoot\src\Server\ZB.MOM.WW.OtOpcUa.Host" `
|
|
-c Release -o (Join-Path $PublishRoot "lmxopcua") | Out-Null
|
|
& dotnet publish "$RepoRoot\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware" `
|
|
-c Release -o (Join-Path $PublishRoot "lmxopcua\WonderwareHistorian") | Out-Null
|
|
} "dotnet publish (Host + sidecar)"
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 4b: Assert the Wonderware historian sidecar deploy is COMPLETE
|
|
# ------------------------------------------------------------------------
|
|
# A partial/stale sidecar deploy (e.g. a hand-copy that bypassed the
|
|
# `dotnet publish` above) silently drops the net48 binding-redirect
|
|
# .exe.config and the transitive runtime DLLs MessagePack needs — most
|
|
# notably System.Memory.dll. The sidecar then JIT-load-faults inside
|
|
# TcpFrameServer.RunOneConnectionAsync ("FileNotFoundException: System.Memory,
|
|
# Version=4.0.1.2") and NSSM crash-loops it (exit 2 every ~2 min, ~120 s of
|
|
# retry backoff before it gives up). Fail loudly here so an incomplete deploy
|
|
# is caught at publish time instead of by a production crash-loop.
|
|
# (Root-caused 2026-06-12; these four files have no alternate resolution path,
|
|
# unlike the AVEVA SDK DLLs which the Historian install can also supply.)
|
|
|
|
if (-not $WhatIf) {
|
|
Step "Verifying Wonderware historian sidecar deploy is complete"
|
|
|
|
$sidecarDir = Join-Path $PublishRoot "lmxopcua\WonderwareHistorian"
|
|
$sidecarRequired = @(
|
|
'OtOpcUa.Driver.Historian.Wonderware.exe',
|
|
'OtOpcUa.Driver.Historian.Wonderware.exe.config', # net48 binding redirects
|
|
'System.Memory.dll', # MessagePack's load-bearing dep
|
|
'MessagePack.dll'
|
|
)
|
|
|
|
$missing = $sidecarRequired | Where-Object { -not (Test-Path (Join-Path $sidecarDir $_)) }
|
|
|
|
if ($missing.Count -gt 0) {
|
|
$msg = "Wonderware historian sidecar deploy at '$sidecarDir' is INCOMPLETE — missing: " +
|
|
"$($missing -join ', '). It would crash-loop with a FileNotFoundException in PipeServer. " +
|
|
"Re-run the sidecar 'dotnet publish' into that folder (Step 4) — never hand-copy a partial build output."
|
|
throw $msg
|
|
}
|
|
|
|
Write-Host " sidecar deploy OK — all $($sidecarRequired.Count) load-bearing files present" -ForegroundColor DarkGreen
|
|
}
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 5: Service env block — ensure OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED
|
|
# is set on the Wonderware historian service (PR C.2 toggle).
|
|
# Note: the TCP transport env (OTOPCUA_HISTORIAN_TCP_PORT, OTOPCUA_HISTORIAN_BIND,
|
|
# OTOPCUA_HISTORIAN_TLS_ENABLED, OTOPCUA_HISTORIAN_TLS_CERT) and the Windows
|
|
# Firewall rule are set once at install time by Install-Services.ps1 and are
|
|
# not touched here on refresh; re-run Install-Services.ps1 to change them.
|
|
# OTOPCUA_ALLOWED_SID is no longer used (TCP transport retired the named pipe).
|
|
# ------------------------------------------------------------------------
|
|
|
|
if (Test-NssmService 'OtOpcUaWonderwareHistorian') {
|
|
Step "Ensuring OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED is set on the historian service"
|
|
|
|
Run {
|
|
$existing = nssm get OtOpcUaWonderwareHistorian AppEnvironmentExtra
|
|
if ($existing -notmatch 'OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED') {
|
|
$combined = $existing + "`r`nOTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true"
|
|
nssm set OtOpcUaWonderwareHistorian AppEnvironmentExtra $combined | Out-Null
|
|
Write-Host " appended OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true" -ForegroundColor DarkGreen
|
|
}
|
|
else {
|
|
Write-Host " already present; leaving service env block untouched"
|
|
}
|
|
} "patch service env block"
|
|
}
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 6: Start in forward dependency order
|
|
# ------------------------------------------------------------------------
|
|
|
|
Step "Starting services (MxAccessGw > OtOpcUaWonderwareHistorian > OtOpcUaHost)"
|
|
|
|
foreach ($pair in @(
|
|
@{ Name = 'MxAccessGw'; Wait = 4 },
|
|
@{ Name = 'OtOpcUaWonderwareHistorian'; Wait = 4 },
|
|
@{ Name = 'OtOpcUaHost'; Wait = 8 }
|
|
)) {
|
|
$name = $pair.Name
|
|
if (Test-NssmService $name) {
|
|
Run { Start-Service $name } "start $name"
|
|
if (-not $WhatIf) { Start-Sleep -Seconds $pair.Wait }
|
|
}
|
|
else {
|
|
Write-Host " ($name not installed; skipping)" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Step 7: Smoke verification
|
|
# ------------------------------------------------------------------------
|
|
|
|
Step "Smoke verification"
|
|
|
|
if (-not $WhatIf) {
|
|
foreach ($name in @('MxAccessGw', 'OtOpcUaWonderwareHistorian', 'OtOpcUaHost')) {
|
|
if (Test-NssmService $name) {
|
|
$status = (Get-Service $name).Status
|
|
$color = if ($status -eq 'Running') { 'Green' } else { 'Red' }
|
|
Write-Host " $name = $status" -ForegroundColor $color
|
|
}
|
|
}
|
|
|
|
foreach ($port in @(5120, 4840, 4841)) {
|
|
$listening = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
|
|
$color = if ($listening) { 'Green' } else { 'DarkYellow' }
|
|
Write-Host " TCP $port listening = $($null -ne $listening)" -ForegroundColor $color
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host " Recent log tails:" -ForegroundColor DarkCyan
|
|
$tails = @(
|
|
"$PublishRoot\lmxopcua\logs\otopcua-*.log",
|
|
"$PublishRoot\mxaccessgw\stdout.log",
|
|
"$env:ProgramData\OtOpcUa\historian-wonderware-*.log"
|
|
)
|
|
foreach ($pattern in $tails) {
|
|
$latest = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue |
|
|
Sort-Object LastWriteTime -Descending |
|
|
Select-Object -First 1
|
|
if ($null -ne $latest) {
|
|
Write-Host ""
|
|
Write-Host " --- $($latest.FullName) (last 10 lines) ---" -ForegroundColor DarkGray
|
|
Get-Content $latest.FullName -Tail 10 | ForEach-Object { Write-Host " $_" }
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Refresh complete." -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host "Next: run the functional verification scenarios from"
|
|
Write-Host " docs\plans\alarms-over-gateway.md §Track D §6 'Functional verification'"
|
|
Write-Host " - Galaxy-native alarm raise"
|
|
Write-Host " - Scripted alarm → AVEVA Historian round-trip"
|
|
Write-Host " - Sub-attribute fallback path with IAlarmSource disabled"
|