using Microsoft.Win32; using System.Security; namespace Zcbot.WindowsNode; internal sealed record OriginRuntimeStatus( string Software, string? SoftwareVersion, string AdapterVersion, string Health, string Detail); internal static class OriginRuntimeProbe { private static readonly Lazy Current = new(DetectCore); private const string AutomationProgId = @"Origin.ApplicationSI\CLSID"; internal static OriginRuntimeStatus Detect() => Current.Value; private static OriginRuntimeStatus DetectCore() { try { var version = FindInstalledVersion(); using var automationKey = Registry.ClassesRoot.OpenSubKey(AutomationProgId); var automationRegistered = automationKey is not null; if (version is null && !automationRegistered) { return Status(null, "unavailable", "未检测到 Origin/OriginPro 安装"); } if (!automationRegistered) { return Status(version, "unavailable", "已检测到 Origin,但 COM 自动化组件未注册"); } if (!Environment.UserInteractive) { return Status(version, "unavailable", "Origin 需要交互式 Windows 桌面会话"); } var interpreter = OriginWorkerRuntime.ResolveInterpreter(); if (interpreter is null) { return Status( version, "unavailable", "Origin 可用,但固定 Python 运行时缺失;请配置 ZCBOT_ORIGIN_PYTHON"); } return Status(version, "ready", $"Origin COM 与固定 Python 运行时可用({interpreter})"); } catch (Exception exception) when ( exception is SecurityException or UnauthorizedAccessException or IOException) { return Status(null, "unavailable", $"Origin 运行时探测失败:{exception.Message}"); } } private static OriginRuntimeStatus Status(string? version, string health, string detail) => new("OriginPro", version, "0.4.0", health, detail); private static string? FindInstalledVersion() { var candidates = new List(); foreach (var hive in new[] { RegistryHive.LocalMachine, RegistryHive.CurrentUser }) { foreach (var view in new[] { RegistryView.Registry64, RegistryView.Registry32 }) { using var baseKey = RegistryKey.OpenBaseKey(hive, view); using var uninstall = baseKey.OpenSubKey( @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"); if (uninstall is null) { continue; } foreach (var keyName in uninstall.GetSubKeyNames()) { using var product = uninstall.OpenSubKey(keyName); var name = product?.GetValue("DisplayName") as string; var publisher = product?.GetValue("Publisher") as string; if (!IsOriginProduct(name, publisher)) { continue; } var version = product?.GetValue("DisplayVersion") as string; if (!string.IsNullOrWhiteSpace(version)) { candidates.Add(version.Trim()); } } } } return candidates.OrderByDescending(ParseVersion).ThenByDescending(x => x).FirstOrDefault(); } private static bool IsOriginProduct(string? name, string? publisher) => !string.IsNullOrWhiteSpace(name) && (name.Equals("Origin", StringComparison.OrdinalIgnoreCase) || name.StartsWith("Origin ", StringComparison.OrdinalIgnoreCase) || name.StartsWith("OriginPro", StringComparison.OrdinalIgnoreCase)) && (publisher?.Contains("OriginLab", StringComparison.OrdinalIgnoreCase) ?? false); private static Version ParseVersion(string value) => Version.TryParse(value, out var version) ? version : new Version(0, 0); }