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.");
}
}