<# .SYNOPSIS Registers the v2 Windows services on a node: OtOpcUa (main server, net10) and optionally OtOpcUaWonderwareHistorian (Wonderware historian sidecar). .DESCRIPTION PR 7.2 retired the legacy out-of-process OtOpcUaGalaxyHost service alongside the GalaxyProxyDriver / GalaxyHost / GalaxyShared projects. Galaxy access now flows through the in-process GalaxyDriver talking gRPC to a separately-installed mxaccessgw. The mxaccessgw server runs out of its own repo (`c:\Users\dohertj2\Desktop\mxaccessgw\`) — see `docs/v2/Galaxy.ParityRig.md` for the gw setup recipe. .PARAMETER InstallRoot Where the binaries live (typically C:\Program Files\OtOpcUa). .PARAMETER ServiceAccount Service account SID or DOMAIN\name. The OtOpcUa service runs under this account. .PARAMETER InstallWonderwareHistorian Gate the OtOpcUaWonderwareHistorian sidecar install. Off by default; set when the deployment uses the Wonderware historian for history reads + alarm-event persistence. .PARAMETER HistorianSharedSecret Per-process secret passed to the Historian sidecar via env var. Generated freshly per install when not supplied. .EXAMPLE .\Install-Services.ps1 -InstallRoot 'C:\Program Files\OtOpcUa' -ServiceAccount 'OTOPCUA\svc-otopcua' .EXAMPLE .\Install-Services.ps1 -InstallRoot 'C:\Program Files\OtOpcUa' -ServiceAccount 'OTOPCUA\svc-otopcua' ` -InstallWonderwareHistorian #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$InstallRoot, [Parameter(Mandatory)] [string]$ServiceAccount, # PR 3.W — Wonderware historian sidecar. Optional; gates the # OtOpcUaWonderwareHistorian service. Secret + pipe defaults match the server's # Historian:Wonderware appsettings block. [switch]$InstallWonderwareHistorian, [string]$HistorianSharedSecret, [string]$HistorianPipeName = 'OtOpcUaWonderwareHistorian', [string]$HistorianServer = 'localhost', [int]$HistorianPort = 32568, [string[]]$AvevaServiceDependencies = @('NmxSvc', 'aaBootstrap', 'aaGR') ) $ErrorActionPreference = 'Stop' if (-not (Test-Path "$InstallRoot\OtOpcUa.Server.exe")) { Write-Error "OtOpcUa.Server.exe not found at $InstallRoot — copy the publish output first" exit 1 } # Generate fresh shared secrets per install if not supplied. function New-SharedSecret { $bytes = New-Object byte[] 32 [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) return [Convert]::ToBase64String($bytes) } if ($InstallWonderwareHistorian -and -not $HistorianSharedSecret) { $HistorianSharedSecret = New-SharedSecret } if ($InstallWonderwareHistorian -and -not (Test-Path "$InstallRoot\WonderwareHistorian\OtOpcUa.Driver.Historian.Wonderware.exe")) { Write-Error "OtOpcUa.Driver.Historian.Wonderware.exe not found at $InstallRoot\WonderwareHistorian — copy the publish output first" exit 1 } # Resolve the SID — the IPC ACL needs the SID, not the down-level name. $sid = if ($ServiceAccount.StartsWith('S-1-')) { $ServiceAccount } else { (New-Object System.Security.Principal.NTAccount $ServiceAccount).Translate([System.Security.Principal.SecurityIdentifier]).Value } # --- Install OtOpcUaWonderwareHistorian (PR 3.W) — separate sidecar that exposes the # Wonderware Historian SDK via a named-pipe protocol consumed by the .NET 10 server. # Optional: only installed when -InstallWonderwareHistorian is supplied. Depends on the # hard AVEVA services that host the historian SDK runtime path. $historianDepend = $null if ($InstallWonderwareHistorian) { $historianEnv = @( "OTOPCUA_HISTORIAN_PIPE=$HistorianPipeName" "OTOPCUA_ALLOWED_SID=$sid" "OTOPCUA_HISTORIAN_SECRET=$HistorianSharedSecret" "OTOPCUA_HISTORIAN_ENABLED=true" # Default-on when the historian sidecar is installed; flip to false for a # read-only deployment that still loads aahClientManaged for reads but # rejects WriteAlarmEvents frames. "OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true" "OTOPCUA_HISTORIAN_SERVER=$HistorianServer" "OTOPCUA_HISTORIAN_PORT=$HistorianPort" ) -join "`0" $historianEnv += "`0`0" Write-Host "Installing OtOpcUaWonderwareHistorian..." & sc.exe create OtOpcUaWonderwareHistorian binPath= "`"$InstallRoot\WonderwareHistorian\OtOpcUa.Driver.Historian.Wonderware.exe`"" ` DisplayName= 'OtOpcUa Wonderware Historian Sidecar (out-of-process aahClient)' ` start= auto ` depend= ($AvevaServiceDependencies -join '/') ` obj= $ServiceAccount | Out-Null & sc.exe config OtOpcUaWonderwareHistorian start= delayed-auto | Out-Null $svcKey = "HKLM:\SYSTEM\CurrentControlSet\Services\OtOpcUaWonderwareHistorian" $envValue = $historianEnv.Split("`0") | Where-Object { $_ -ne '' } Set-ItemProperty -Path $svcKey -Name 'Environment' -Type MultiString -Value $envValue $historianDepend = 'OtOpcUaWonderwareHistorian' } # --- Install OtOpcUa. Galaxy access flows through GalaxyDriver → mxaccessgw (gRPC), # so OtOpcUa no longer depends on a sibling service for Galaxy connectivity. The # mxaccessgw is installed separately. When the Wonderware sidecar is installed, # depend on it for startup ordering. $otOpcUaDepends = @() if ($historianDepend) { $otOpcUaDepends += $historianDepend } Write-Host "Installing OtOpcUa..." $createArgs = @( 'create', 'OtOpcUa', 'binPath=', "`"$InstallRoot\OtOpcUa.Server.exe`"", 'DisplayName=', 'OtOpcUa Server', 'start=', 'auto', 'obj=', $ServiceAccount ) if ($otOpcUaDepends.Count -gt 0) { $createArgs += @('depend=', ($otOpcUaDepends -join '/')) } & sc.exe @createArgs | Out-Null Write-Host "" Write-Host "Installed. Start with:" if ($InstallWonderwareHistorian) { Write-Host " sc.exe start OtOpcUaWonderwareHistorian" } Write-Host " sc.exe start OtOpcUa" if ($InstallWonderwareHistorian) { Write-Host "" Write-Host "Wonderware historian shared secret (configure into appsettings.json Historian:Wonderware:SharedSecret):" Write-Host " $HistorianSharedSecret" } Write-Host "" Write-Host "NOTE: Galaxy access flows through mxaccessgw — install + run that separately" Write-Host " per docs/v2/Galaxy.ParityRig.md. OtOpcUa connects via the Galaxy.Gateway" Write-Host " section of appsettings.json (default endpoint http://localhost:5120)."