[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 (OtOpcUa → OtOpcUaWonderwareHistorian → MxAccessGw)" foreach ($name in @('OtOpcUa', 'OtOpcUaWonderwareHistorian', 'MxAccessGw')) { if (Test-NssmService $name) { Run { nssm stop $name } "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.Server, 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 server + Wonderware historian sidecar from $RepoRoot" Run { & dotnet publish "$RepoRoot\src\ZB.MOM.WW.OtOpcUa.Server" ` -c Release -o (Join-Path $PublishRoot "lmxopcua") | Out-Null & dotnet publish "$RepoRoot\src\ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware" ` -c Release -o (Join-Path $PublishRoot "lmxopcua\WonderwareHistorian") | Out-Null } "dotnet publish (Server + sidecar)" # ------------------------------------------------------------------------ # Step 5: Service env block — ensure OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED # is set on the Wonderware historian service (PR C.2 toggle). # ------------------------------------------------------------------------ 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 → OtOpcUa)" foreach ($pair in @( @{ Name = 'MxAccessGw'; Wait = 4 }, @{ Name = 'OtOpcUaWonderwareHistorian'; Wait = 4 }, @{ Name = 'OtOpcUa'; Wait = 8 } )) { $name = $pair.Name if (Test-NssmService $name) { Run { nssm start $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', 'OtOpcUa')) { 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"