Problem Overview
An OPC Classic Data Access (DA) client written in .NET C# throws an unhandled exception at the first call to myServer.Connect() when targeting a Siemens PCS7 station. The exception fires before any tag read, browse, or subscription call, which means the failure is at the COM/DCOM transport layer, not the data access layer. The most common version of the code that produces this fault is:
using Opc;
using Opc.Da;
Opc.URL serverURL = new Opc.URL("opc.tcp://FaraTarh-PC:4845");
var myServer = new Opc.Da.Server(new OpcCom.Factory(), serverURL);
myServer.Connect(); // <- throws unhandled exception
The visible symptom is one of three exception types:
-
Opc.ConnectFailedExceptionwith a message containing "Could not create remote server" or "Access is denied". -
System.Runtime.InteropServices.COMExceptionwith HRESULTs such as0x80070005(E_ACCESSDENIED),0x800706BA(RPC_S_SERVER_UNAVAILABLE), or0x80040154(REGDB_E_CLASSNOTREG). -
System.InvalidCastExceptionorNotImplementedExceptionraised inside the OPC Foundation .NET stack when the URL object is parsed against the wrong factory.
The exception happens before any return value is produced. The developer cannot inspect IsConnected, GetStatus(), or browse the address space because the COM channel was never established. This is structurally different from a logic bug inside a tag subscription or read cycle, and it must be fixed at the connection setup layer.
Root Cause Analysis: The URL/Factory Mismatch
The single highest-probability root cause for an unhandled exception in this exact code pattern is a structural mismatch between the URL scheme and the factory class. The OPC Foundation .NET API (the legacy OpcNetApi.dll / OpcNetCom.dll stack) defines distinct URL schemes for distinct transports, and each factory only accepts its own scheme.
| URL Scheme | Transport | Factory Class | API Namespace |
|---|---|---|---|
opcda:// |
COM/DCOM (Classic OPC DA) | OpcCom.Factory() |
Opc.Da |
opcae:// |
COM/DCOM (Alarms & Events) | OpcCom.Factory() |
Opc.Ae |
opchda:// |
COM/DCOM (Historical Data Access) | OpcCom.Factory() |
Opc.Hda |
http:// |
XML-DA (SOAP/HTTP) | OpcXml.Factory() |
Opc.Da |
opc.tcp:// |
OPC UA binary (port 4840 default) | n/a (legacy API does not support UA) | Not in Opc.Da
|
opc.wss:// |
OPC UA over WebSockets | n/a (legacy API does not support UA) | Not in Opc.Da
|
The reported code uses opc.tcp://FaraTarh-PC:4845 as the URL. That scheme belongs to OPC Unified Architecture and is parsed correctly only by the OPC UA .NET Standard stack (the OPCFoundation.NetStandard.Opc.Ua NuGet package and its Opc.Ua.Client session layer). When the OpcCom.Factory receives an opc.tcp:// URL, the internal Parse() method throws because the COM factory expects an opcda://, opcae://, or opchda:// prefix. The exception surfaces as an unhandled error at Connect() because the URL parser runs inside that call.
Reference: OPC Foundation - What is OPC? defines the canonical mapping of URL schemes to transport layers. The classic opcda:// scheme is the only valid input for an Opc.Da.Server instantiated with OpcCom.Factory().
There are three further root causes that frequently co-exist with the URL/Factory mismatch and must be ruled out independently:
-
Wrong ProgID or CLSID for SIMATIC NET. PCS7 does not expose an OPC DA server at the IP/hostname alone. The URL must include the registered ProgID of the SIMATIC NET OPC server, which is
OPC.SimaticNET. A bareopcda://FaraTarh-PCresolves to no installed class. -
SIMATIC NET PC station not configured. The OPC server process is
servs7opc.exeand is registered only when the PC station has been set up in the Station Configuration Editor and the configuration has been downloaded. Without this step the ProgID exists in the registry but the COM server fails to start. -
DCOM authentication mismatch after a Windows security update. Starting with Windows 10 1709 and Windows Server 2019, the default DCOM authentication level is set to
Packet Integrityand the default impersonation level toIdentify. A SIMATIC NET OPC server older than the matching SIMATIC NET hotfix will reject the call withE_ACCESSDENIED (0x80070005).
OPC DA URL Schemes and Connection Architecture
Classic OPC DA uses Microsoft's COM and DCOM as its wire protocol. There is no TCP socket in the user code path. The OpcCom.Factory instantiates a COM proxy that goes through the Windows RPC runtime to the OPC server process. The URL string is used by the factory to build the COSERVERINFO and MULTI_QI structures consumed by CoCreateInstanceEx.
The canonical URL grammar accepted by the Opc.URL parser for COM DA is:
opcda://[host[:port]]/[ProgID | !CLSID][;option=value[;...]]
Common forms observed in PCS7 deployments:
opcda://localhost/OPC.SimaticNET
opcda://FaraTarh-PC/OPC.SimaticNET
opcda://10.20.30.40/OPC.SimaticNET
opcda://FaraTarh-PC/!{8D9C1992-91C7-4E29-9C73-3DCDB3B36C3C}
Supported options include CLSID (explicit override), UserName, Password, Domain, and Culture. The username/password options are used to force a specific DCOM identity when the client is running as a service account.
The legacy OPC .NET API redistributable is hosted on the OPC Foundation GitHub at UA-.NET-Legacy and is the source of the OpcNetApi.dll and OpcNetCom.dll assemblies used by the source code. It is fully COM-based and contains no UA client implementation, despite the GitHub repository name.
Correct Connection Pattern in C#
The first fix is to replace the URL scheme. The second is to wrap the connection in a typed exception handler that captures the HRESULT, because a bare myServer.Connect() discards the structured information that the .NET COM interop layer produces. The following pattern is the field-proven baseline for a PCS7 DA client:
using Opc;
using Opc.Da;
using System;
using System.Runtime.InteropServices;
public static class Pcs7OpcDaClient
{
public static ServerStatus ConnectAndReport(string host, string progId)
{
Opc.URL url = null;
Opc.Da.Server server = null;
try
{
url = new Opc.URL($"opcda://{host}/{progId}");
server = new Opc.Da.Server(new OpcCom.Factory(url), null);
server.Connect();
if (!server.IsConnected)
{
throw new InvalidOperationException(
"Connect() returned without throwing but IsConnected is false.");
}
ServerStatus status = server.GetStatus();
Console.WriteLine($"Vendor: {status.VendorInfo}");
Console.WriteLine($"State: {status.ServerState}");
Console.WriteLine($"Version: {status.MajorVersion}.{status.MinorVersion}");
Console.WriteLine($"Start time: {status.StartTime}");
return status;
}
catch (Opc.ConnectFailedException cfx)
{
LogStructured("Opc.ConnectFailedException", cfx.Message, null);
throw;
}
catch (COMException cex)
{
int hr = Marshal.GetHRForException(cex);
string facility = (hr & 0xFFFF0000) == 0x80070000
? "Win32"
: (hr & 0xFFFF0000) == 0x80040000 ? "OLE"
: (hr & 0xFFFF0000) == 0x80000000 ? "HRESULT" : "Unknown";
LogStructured("COMException", cex.Message, $"0x{hr:X8} [{facility}]");
throw;
}
finally
{
if (server != null)
{
try { server.Disconnect(); } catch { /* ignore */ }
server.Dispose();
}
}
}
private static void LogStructured(string type, string msg, string code)
{
Console.WriteLine($"[{DateTime.UtcNow:O}] {type} {(code ?? "")} - {msg}");
}
}
Notes on the pattern:
- The
OpcCom.Factoryconstructor is overloaded. The two-argument formnew OpcCom.Factory(url)carries the URL into the factory so the COM proxy is created with the correctCOSERVERINFOon the first call. The single-argument form is also valid; both are equivalent when the same URL is passed tonew Opc.Da.Server(...)as the second argument. -
server.IsConnectedis the only reliable indicator of a successfulConnect(). Do not rely on a non-throwing return value; in older versions of the .NET API,Connect()can returnnulland leave the wrapper in an indeterminate state. - The
finallyblock is mandatory. The COM proxy holds a reference to the server process; failing toDisconnect()leaks DCOM handles and can lock theOPC.SimaticNETserver in a "starting" state across reconnects.
Siemens PCS7 OPC Server Configuration
The OPC DA server in a PCS7 environment is provided by SIMATIC NET (part number 6GK1704-xCWxx-xAAx depending on the licence tier). The server process is servs7opc.exe and the ProgID is OPC.SimaticNET. The server only starts when the PC station has been fully configured.
Required configuration steps before any client can connect:
- Open Station Configuration Editor on the PCS7 engineering station or runtime station.
- Add a PC station matching the local Windows hostname. The Windows Computer Name must equal the station name, otherwise
CoCreateInstanceExroutes the call to a non-existent endpoint and returns0x800706BA. - Insert at least one OPC server module under the PC station, typically OPC Server > SW V7.1 or the version bundled with the installed SIMATIC NET release.
- Insert a IE/PB or IE/PN interface module with a configured S7 connection to the AS (automation station). Without an active S7 connection the OPC server starts but the address space is empty and all reads return
OPC_QUALITY_BAD. - Click Station > Save and Compile, then Download to PC station. The download writes the
OPC.SimaticNETregistration toHKEY_CLASSES_ROOT. - Restart the SIMATIC NET OPC server Windows service (display name: S7-OPCS, service name: s7opcsx) or the whole PC station.
- Verify with the SIMATIC NET OPC Scout tool: connect to
opcda://localhost/OPC.SimaticNET, browse to S7:[S7 connection_1], and read a known tag. If OPC Scout connects, the C# client must also connect.
The official SIMATIC NET programming manual is on the Siemens support portal under entry ID 109751706 ("SIMATIC NET - OPC Server - Programming Manual"). The manual documents the OPC.SimaticNET ProgID, the address space hierarchy, and the supported data types. Always verify against the manual revision matching the installed SIMATIC NET version, because ProgIDs and the S7 connection naming have changed across major releases.
CoCreateInstanceEx will succeed against the CLSID but the COM proxy will hang because the S7 OPC service cannot resolve its own name. Symptom: Connect() blocks for the entire DCOM timeout (default 60 seconds) and then throws with HRESULT 0x800706BA.
DCOM Security and Windows Hardening
DCOM is the most failure-prone layer in any classic OPC DA deployment. Windows 10 1709 (Fall Creators Update) and Windows Server 2019 changed the default DCOM authentication to RPC_C_AUTHN_LEVEL_PKT_INTEGRITY and the default impersonation to RPC_C_IMP_LEVEL_IDENTIFY. A SIMATIC NET OPC server older than the matching hotfix (specifically SIMATIC NET V14 SP1 Update 6 and later) cannot negotiate the new defaults and rejects the connection with E_ACCESSDENIED (0x80070005).
To rule DCOM in or out as the cause, follow this sequence on both the client PC and the PCS7 station:
- Open
dcomcnfg→ Component Services → Computers → My Computer. - Right-click My Computer → Properties → Default Properties tab.
- Set Enable Distributed COM on this computer = on.
- Set Default Authentication Level = Connect for diagnostic testing only. Revert to Packet Integrity once the issue is resolved.
- Set Default Impersonation Level = Identify.
- Expand DCOM Config, locate OPC.SimaticNET (or its CLSID
{8D9C1992-91C7-4E29-9C73-3DCDB3B36C3C}), right-click → Properties.- General tab: Authentication Level = Default (inherits the machine default).
- Location tab: enable Run application on this computer; for remote access also enable Run application on the following computer and enter the target host.
- Security tab: configure Launch and Activation Permissions, Access Permissions, and Configuration Permissions to grant the client user or service account the rights to launch, access, and configure the COM object.
- Identity tab: for a service-style client select This user and enter an account with the rights described above; for an interactive client select The launching user.
- On the firewall, allow inbound on TCP 135 (RPC Endpoint Mapper) and on the dynamic range 49152-65535. Restrict by source IP to the client subnet.
- Reboot the PCS7 station.
dcomcnfgwrites toHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Oleand to per-appid keys, and several OPC servers cache the values on startup.
For the underlying RPC authentication model, see the Microsoft RPC documentation at Authentication Level Constants. The same reference documents the RPC_C_AUTHN_LEVEL_PKT_INTEGRITY constant used by recent Windows builds.
When the issue is specifically that the Opc.Da.Server.Connect() call returns 0x80070005 immediately after a Windows update, the documented mitigation is either to update SIMATIC NET to a release that supports the new default, or to lower the default authentication level to Connect for the OPC.SimaticNET appid. The former is the production-safe path; the latter is acceptable only for an isolated test cell.
Diagnostic Workflow for Opc.Da.Server.Connect Failures
When the exception does not directly identify the layer, the workflow below is the standard triage path for engineers commissioning a PCS7 OPC DA client. Each step produces an observable that either confirms or eliminates the next layer.
-
Verify the URL parser. Construct the
Opc.URLobject in a try/catch and printurl.Scheme,url.HostName,url.Port, andurl.Path. Ifurl.Schemeis"opc.tcp", the URL will not work withOpcCom.Factory; fix the scheme before doing anything else. -
Verify the ProgID is registered on the client machine. Open
regeditand navigate toHKEY_CLASSES_ROOT\OPC.SimaticNET. If the key does not exist, install SIMATIC NET on the client or change the URL to point to the PCS7 station as the server host. The ProgID must be on the machine that runs the client code only if the client is creating the COM object on the local machine; for remote creation, only the server needs the key. -
Verify the OPC server is running on the target machine. On the PCS7 station, open Task Manager and confirm
servs7opc.exeis present. If not, restart the S7-OPCS service and inspect the Windows Event log under Applications and Services Logs → SIMATIC NET. - Use the SIMATIC NET OPC Scout to connect from the same machine that runs the OPC.SimaticNET server. If OPC Scout connects, the OPC server is healthy. The next test is to connect OPC Scout from the client machine; this isolates whether DCOM routing is at fault.
-
Use the C# client with verbose exception logging as shown in the pattern above. Capture the full HRESULT and the inner exception chain. A bare
try { myServer.Connect(); } catch { /* swallow */ }is the single most common cause of "it doesn't work and I have no idea why". -
Use
dcomcnfgon both machines to set the default authentication to Connect temporarily. If the connection now succeeds, the issue is authentication negotiation; revert the change and patch SIMATIC NET. -
Use
tracertandtelnet <host> 135to confirm RPC reachability.telnetis not a full DCOM test, but a closed port 135 is a definitive negative.
Step 6 in particular separates "DCOM is misconfigured" from "the OPC server itself is failing". The two produce different HRESULTs and require different remediations; do not skip the test.
Exception Types and Error Code Mapping
The exception types raised by the OPC .NET API and the underlying COM interop are the most precise signal of where the failure sits. The mapping below is the engineer's quick reference.
| Exception | HRESULT | Likely Layer | First Action |
|---|---|---|---|
Opc.ConnectFailedException |
varies | URL parse or CoCreateInstanceEx
|
Print URL fields; verify scheme and ProgID. |
COMException |
0x80070005 E_ACCESSDENIED |
DCOM security | Check dcomcnfg access and launch permissions; check Windows auth level default. |
COMException |
0x800706BA RPC_S_SERVER_UNAVAILABLE |
Network or service | Check servs7opc.exe is running; check TCP 135 reachability; verify PC station name. |
COMException |
0x80040154 REGDB_E_CLASSNOTREG |
COM registration | Install or re-register SIMATIC NET; verify ProgID in HKEY_CLASSES_ROOT. |
COMException |
0x8001011A RPC_E_DISCONNECTED |
DCOM session dropped | Server crashed; check SIMATIC NET event log; increase client retry interval. |
COMException |
0x8000401A CO_E_RUNAS_LOGON_FAILURE |
Identity configuration | Verify the configured DCOM identity account; check the password is set and not expired. |
InvalidCastException |
n/a | API surface mismatch | Check that opcda:// is used; OpcCom.Factory will not instantiate against an opc.tcp:// URL. |
NotImplementedException |
n/a | Wrong factory for scheme | URL scheme belongs to OPC UA; legacy API cannot service it. |
The HRESULT 0xC0040004 and similar 0xC004xxxx codes are OPC Foundation specific; they are returned by the OPC server itself, not by Windows. When the client logs one of these, the OPC server is reachable and the failure is in the server-side data path (e.g. the S7 connection is down). Refer to the SIMATIC NET diagnostic manual for the exact meaning of each 0xC004xxxx code.
Modernization Path: Migrating to OPC UA .NET Standard
The legacy OPC .NET API used in the source code is the only Microsoft-supported way to consume classic OPC DA from .NET Framework. It is not available for .NET Core, .NET 5+, or .NET Standard 2.0. It is also no longer the recommended path for new PCS7 integrations because PCS7 V9 and later expose a native OPC UA server through SIMATIC NET, and the OPC Foundation publishes a maintained .NET Standard client stack.
The migration targets are:
| Property | OPC DA (legacy) | OPC UA (.NET Standard) |
|---|---|---|
| Wire protocol | COM/DCOM over RPC | TCP binary, optional SOAP/HTTPS |
| Default port | RPC 135 + dynamic range | 4840 (configurable) |
| Security | Windows DCOM ACLs | Application-instance certificates, X.509 trust |
| Cross-platform | Windows only | Windows, Linux, macOS, embedded |
| Client library |
OpcNetApi.dll + COM |
OPCFoundation.NetStandard.Opc.Ua NuGet |
| PCS7 support | SIMATIC NET ≤ V18 | SIMATIC NET ≥ V14 SP1 (server-side) |
The equivalent minimal UA session for a PCS7 station is:
using Opc.Ua;
using Opc.Ua.Client;
var config = new ApplicationConfiguration()
{
ApplicationName = "Pcs7UaClient",
ApplicationType = ApplicationType.Client,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier
{ StoreType = "Directory", StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/MachineDefault" },
TrustedPeerCertificates = new CertificateIdentifier
{ StoreType = "Directory", StorePath = "%CommonApplicationData%/OPC Foundation/CertificateStores/UA Certificate Authorities" },
},
ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 },
};
await config.Validate(ApplicationType.Client);
var endpoint = CoreClientUtils.SelectEndpoint("opc.tcp://FaraTarh-PC:4840", useSecurity: false);
var session = await Session.Create(config, new ConfiguredEndpoint(null, endpoint, false), false, "Pcs7UaClient", 60000, null, null);
Console.WriteLine($"Connected to {session.EndpointDescription.Server.ApplicationName}");
Console.WriteLine($"State: {session.EndpointDescription.Server.ServerState}");
DataValue value = session.ReadValue("ns=2;s=S7:[S7_Connection_1]DB1,W0");
Console.WriteLine($"Value: {value.Value} (status: {value.StatusCode})");
await session.CloseAsync();
The URL scheme is now correctly opc.tcp:// and the security model is per-application certificate rather than per-machine DCOM ACL. The discovery endpoint on a PCS7 station is typically opc.tcp://<host>:4840 when the SIMATIC NET OPC UA server is enabled in the Station Configuration Editor.
The OPC UA .NET Standard library is the official OPC Foundation client, available on NuGet as OPCFoundation.NetStandard.Opc.Ua and on the OPC Foundation GitHub at UA-.NETStandard. Refer to the OPC UA Reference for the address space, security, and data encoding specifications.
Verification Checklist
After applying the fixes in this article, the following checks confirm that the OPC DA connection is healthy end-to-end.
- Construct the
Opc.URLagainstopcda://<PCS7-host>/OPC.SimaticNET.url.Schemeprints"opcda";url.Pathprints"/OPC.SimaticNET". - Open a console and run
mmc dcomcnfgon the client. Confirm Default Authentication Level is at least Connect and that the SIMATIC NET ProgID is granted access to the calling account. - On the PCS7 station, run
OPC Scoutfrom the SIMATIC NET install and connect toopcda://localhost/OPC.SimaticNET. Browse to S7:[S7_Connection_1] and read one tag. A successful read confirms the OPC server and the S7 connection. - Run the C# client.
myServer.IsConnectedistrueafterConnect().GetStatus()returnsServerState.Runningand a non-zeroStartTime. - Browse the address space with
myServer.Browse(null, null, 0, 100). The result contains a branch for the configured S7 connection. - Subscribe to one tag with a 1000 ms update rate. After one cycle the callback fires with
Quality.Good. - Stop and restart the S7-OPCS service on the PCS7 station. The client's
DataChangedcallback fires withQuality.Badand, after the service is back, fires again withQuality.Goodonce the subscription is re-established. - Capture a Windows performance recorder trace (
wpr -start CPU -start DCOM) while restarting the service. The trace should show the client and server negotiating a new DCOM channel; if no negotiation is visible, the client is using a cached channel that has not been reaped.
If any of the eight checks fails, return to the Diagnostic Workflow section and re-isolate the layer. Do not modify DCOM settings in response to a S7-connection error or vice-versa; the two layers have different remediations and conflating them creates configuration drift.
Frequently Asked Questions
What URL scheme should I use with OpcCom.Factory and Opc.Da.Server?
Use opcda://<host>/<ProgID>. The legacy OPC .NET API only accepts opcda://, opcae://, and opchda:// for COM factories. The opc.tcp:// scheme belongs to OPC UA and must be used with the OPCFoundation.NetStandard.Opc.Ua client, not the legacy API.
Why does my Connect() throw with HRESULT 0x80070005 on Windows 10?
The default DCOM authentication level was raised to Packet Integrity in Windows 10 1709. SIMATIC NET versions before the matching hotfix cannot negotiate this level. Either install the SIMATIC NET hotfix that supports RPC_C_AUTHN_LEVEL_PKT_INTEGRITY or, for diagnostic purposes only, set the default DCOM authentication to Connect in dcomcnfg for the OPC.SimaticNET appid.
What is the ProgID for the SIMATIC NET OPC DA server?
The ProgID is OPC.SimaticNET. The corresponding CLSID is {8D9C1992-91C7-4E29-9C73-3DCDB3B36C3C}. The ProgID is registered in HKEY_CLASSES_ROOT by the SIMATIC NET install after a successful PC station download in the Station Configuration Editor.
How do I check whether the OPC server is reachable before debugging C#?
Use the SIMATIC NET OPC Scout tool to connect to opcda://localhost/OPC.SimaticNET on the PCS7 station itself. If OPC Scout connects from the same host, the server is healthy. Then attempt the same from the client machine. If the local test works and the remote test fails, the issue is in DCOM or network routing, not in the C# code.
Should I migrate my PCS7 OPC client from classic DA to OPC UA?
Yes, for any new integration. OPC UA removes the DCOM dependency, runs on .NET 6+ and Linux, and is the supported path forward from PCS7 V9 with SIMATIC NET V14 SP1 Update 6 or later. Migrate the client to the OPCFoundation.NetStandard.Opc.Ua NuGet package and connect to opc.tcp://<host>:4840. Keep the classic DA client only for legacy systems that cannot be updated.