<# .SYNOPSIS Registers the OtOpcUaFocasHost Windows service. Optional companion to Install-Services.ps1 — only run this on nodes where FOCAS driver instances will run with Tier-C process isolation enabled. .DESCRIPTION FOCAS PR #220 / Tier-C isolation plan. Wraps OtOpcUa.Driver.FOCAS.Host.exe (net48 x86) as a Windows service using NSSM, running under the same service account as the main OtOpcUa service so the named-pipe ACL works. Passes the per-process shared secret via environment variable at service-start time so it never hits disk. .PARAMETER InstallRoot Where the FOCAS Host binaries live (typically C:\Program Files\OtOpcUa\Driver.FOCAS.Host). .PARAMETER ServiceAccount Service account SID or DOMAIN\name. Must match the main OtOpcUa server account so the PipeAcl match succeeds. .PARAMETER FocasSharedSecret Per-process secret passed via env var. Generated freshly per install if not supplied. .PARAMETER FocasBackend Backend selector for the Host process. One of: fwlib32 (default — real Fanuc Fwlib32.dll integration; requires licensed DLL on PATH) fake (in-memory; smoke-test mode) unconfigured (safe default returning structured errors; use until hardware is wired) .PARAMETER FocasPipeName Pipe name the Host listens on. Default: OtOpcUaFocas. .EXAMPLE .\Install-FocasHost.ps1 -InstallRoot 'C:\Program Files\OtOpcUa\Driver.FOCAS.Host' ` -ServiceAccount 'OTOPCUA\svc-otopcua' -FocasBackend fwlib32 #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$InstallRoot, [Parameter(Mandatory)] [string]$ServiceAccount, [string]$FocasSharedSecret, [ValidateSet('fwlib32','fake','unconfigured')] [string]$FocasBackend = 'unconfigured', [string]$FocasPipeName = 'OtOpcUaFocas', [string]$ServiceName = 'OtOpcUaFocasHost', [string]$NssmPath = 'C:\Program Files\nssm\nssm.exe' ) $ErrorActionPreference = 'Stop' function Resolve-Sid { param([string]$Account) if ($Account -match '^S-\d-\d+') { return $Account } try { $nt = New-Object System.Security.Principal.NTAccount($Account) return $nt.Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { throw "Could not resolve '$Account' to a SID. Pass an explicit SID or check the account name." } } if (-not (Test-Path $NssmPath)) { throw "nssm.exe not found at '$NssmPath'. Install NSSM or pass -NssmPath." } $hostExe = Join-Path $InstallRoot 'OtOpcUa.Driver.FOCAS.Host.exe' if (-not (Test-Path $hostExe)) { throw "FOCAS Host binary not found at '$hostExe'. Publish the Driver.FOCAS.Host project first." } if (-not $FocasSharedSecret) { $FocasSharedSecret = [System.Guid]::NewGuid().ToString('N') Write-Host "Generated FocasSharedSecret — store it alongside the OtOpcUa service config." } $allowedSid = Resolve-Sid $ServiceAccount # Idempotent install — remove + re-create if present. $existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if ($existing) { Write-Host "Removing existing '$ServiceName' service..." & $NssmPath stop $ServiceName confirm | Out-Null & $NssmPath remove $ServiceName confirm | Out-Null } & $NssmPath install $ServiceName $hostExe | Out-Null & $NssmPath set $ServiceName DisplayName 'OT-OPC-UA FOCAS Host (Tier-C isolated Fwlib32)' | Out-Null & $NssmPath set $ServiceName Description 'Out-of-process Fwlib32.dll host for OtOpcUa FOCAS driver. Crash-isolated from the main OPC UA server.' | Out-Null & $NssmPath set $ServiceName ObjectName $ServiceAccount | Out-Null & $NssmPath set $ServiceName Start SERVICE_AUTO_START | Out-Null & $NssmPath set $ServiceName AppStdout (Join-Path $env:ProgramData 'OtOpcUa\focas-host-stdout.log') | Out-Null & $NssmPath set $ServiceName AppStderr (Join-Path $env:ProgramData 'OtOpcUa\focas-host-stderr.log') | Out-Null & $NssmPath set $ServiceName AppRotateFiles 1 | Out-Null & $NssmPath set $ServiceName AppRotateBytes 10485760 | Out-Null & $NssmPath set $ServiceName AppEnvironmentExtra ` "OTOPCUA_FOCAS_PIPE=$FocasPipeName" ` "OTOPCUA_ALLOWED_SID=$allowedSid" ` "OTOPCUA_FOCAS_SECRET=$FocasSharedSecret" ` "OTOPCUA_FOCAS_BACKEND=$FocasBackend" | Out-Null & $NssmPath set $ServiceName DependOnService OtOpcUa | Out-Null Write-Host "Installed '$ServiceName' under '$ServiceAccount' (SID=$allowedSid)." Write-Host "Pipe: \\.\pipe\$FocasPipeName Backend: $FocasBackend" Write-Host "Start the service with: Start-Service $ServiceName" Write-Host "" Write-Host "NOTE: the Fwlib32 backend requires the licensed Fwlib32.dll on PATH" Write-Host "alongside the Host exe. See docs/v2/focas-deployment.md."