using System.Runtime.InteropServices; using System.Runtime.Versioning; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.TestSupport.Probes; /// /// Confirms MXAccess COM server registration by resolving the /// LMXProxy.LMXProxyServer ProgID to its CLSID, then checking that the CLSID's /// 32-bit InprocServer32 entry points at a file that exists on disk. /// /// /// A common failure mode on partial installs: ProgID is registered but the CLSID /// InprocServer32 DLL is missing (previous install uninstalled but registry orphan remains). /// This probe surfaces that case with an actionable message instead of the /// 0x80040154 REGDB_E_CLASSNOTREG you'd see from a late COM activation failure. /// public static class MxAccessComProbe { public const string ProgId = "LMXProxy.LMXProxyServer"; public const string VersionedProgId = "LMXProxy.LMXProxyServer.1"; public static PrerequisiteCheck Check() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return new PrerequisiteCheck("com:LMXProxy", PrerequisiteCategory.MxAccessCom, PrerequisiteStatus.Skip, "COM registration probes only run on Windows."); } return CheckWindows(); } [SupportedOSPlatform("windows")] private static PrerequisiteCheck CheckWindows() { try { var (clsid, dll) = RegistryProbe.ResolveProgIdToInproc(ProgId); if (clsid is null) { return new PrerequisiteCheck("com:LMXProxy", PrerequisiteCategory.MxAccessCom, PrerequisiteStatus.Fail, $"ProgID {ProgId} not registered — MXAccess COM server isn't installed. " + $"Install System Platform's MXAccess component and re-run."); } if (string.IsNullOrWhiteSpace(dll)) { return new PrerequisiteCheck("com:LMXProxy", PrerequisiteCategory.MxAccessCom, PrerequisiteStatus.Fail, $"ProgID {ProgId} → CLSID {clsid} but InprocServer32 is empty. " + $"Registry is orphaned; re-register with: regsvr32 /s LmxProxy.dll (from an elevated cmd in the Framework bin dir)."); } // Resolve the recorded path — sometimes registered as a bare filename that the COM // runtime resolves via the current process's DLL-search path. Accept either an // absolute path that exists, or a bare filename whose resolution we can't verify // without loading it (treat as Pass-with-note). if (Path.IsPathRooted(dll)) { if (!File.Exists(dll)) { return new PrerequisiteCheck("com:LMXProxy", PrerequisiteCategory.MxAccessCom, PrerequisiteStatus.Fail, $"ProgID {ProgId} → CLSID {clsid} → InprocServer32 {dll}, but the file is missing. " + $"Re-install the Framework or restore from backup."); } return new PrerequisiteCheck("com:LMXProxy", PrerequisiteCategory.MxAccessCom, PrerequisiteStatus.Pass, $"ProgID {ProgId} → {dll} (file exists)."); } return new PrerequisiteCheck("com:LMXProxy", PrerequisiteCategory.MxAccessCom, PrerequisiteStatus.Pass, $"ProgID {ProgId} → {dll} (bare filename — relies on PATH resolution at COM activation time)."); } catch (Exception ex) { return new PrerequisiteCheck("com:LMXProxy", PrerequisiteCategory.MxAccessCom, PrerequisiteStatus.Warn, $"Probe failed: {ex.GetType().Name}: {ex.Message}"); } } /// /// Warn when running as a 64-bit process — MXAccess COM activation will fail with /// 0x80040154 regardless of registration state. The production drivers run net48 /// x86; xunit hosts run 64-bit by default so this often surfaces first. /// public static PrerequisiteCheck CheckProcessBitness() { if (Environment.Is64BitProcess) { return new PrerequisiteCheck("env:ProcessBitness", PrerequisiteCategory.Environment, PrerequisiteStatus.Warn, "Test host is 64-bit. Direct MXAccess COM activation would fail with REGDB_E_CLASSNOTREG (0x80040154); " + "the production driver workaround is to run Galaxy.Host as a 32-bit process. Tests that only " + "talk to the Host service over the named pipe aren't affected."); } return new PrerequisiteCheck("env:ProcessBitness", PrerequisiteCategory.Environment, PrerequisiteStatus.Pass, "Test host is 32-bit."); } }