NewLife/X

重构 MachineInfo 平台实现为局部部分类,改进GetInfo对WMI的支持。

将 MachineInfo 各平台实现拆分为独立 partial class 文件(Windows、Linux、macOS、容器检测等),主文件仅保留通用逻辑和接口定义。平台相关实现分别迁移至对应文件,提升代码结构清晰度和可维护性,便于后续扩展和维护。
石头 authored at 2026-07-26 09:18:36
6dceb36
Tree
1 Parent(s) 4ce454e
Summary: 6 changed files with 1198 additions and 1128 deletions.
Added +139 -0
Modified +3 -1128
Added +292 -0
Added +49 -0
Added +565 -0
Added +150 -0
Added +139 -0
diff --git a/NewLife.Core/Common/MachineInfo.Container.cs b/NewLife.Core/Common/MachineInfo.Container.cs
new file mode 100644
index 0000000..4b7ebc3
--- /dev/null
+++ b/NewLife.Core/Common/MachineInfo.Container.cs
@@ -0,0 +1,139 @@
+namespace NewLife;
+
+/// <summary>机器信息容器检测部分</summary>
+public partial class MachineInfo
+{
+    #region 容器检测
+    /// <summary>是否运行在容器中</summary>
+    /// <remarks>
+    /// 检测依据:
+    /// 1. 环境变量 DOTNET_RUNNING_IN_CONTAINER
+    /// 2. 存在 /.dockerenv 文件
+    /// 3. /proc/1/cgroup 包含 docker/kubepods/containerd 等关键字
+    /// </remarks>
+    public static Boolean IsInContainer
+    {
+        get
+        {
+            // 环境变量检测
+            var env = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER");
+            if (env.EqualIgnoreCase("true", "1")) return true;
+
+            if (!Runtime.Linux) return false;
+
+            // Docker 环境文件检测
+            if (File.Exists("/.dockerenv")) return true;
+
+            // cgroup 检测
+            var cgroupFile = "/proc/1/cgroup";
+            if (File.Exists(cgroupFile))
+            {
+                try
+                {
+                    var content = File.ReadAllText(cgroupFile);
+                    if (content.Contains("docker") ||
+                        content.Contains("kubepods") ||
+                        content.Contains("containerd") ||
+                        content.Contains("lxc"))
+                        return true;
+                }
+                catch { }
+            }
+
+            return false;
+        }
+    }
+
+    /// <summary>获取容器资源限制</summary>
+    /// <returns>元组:(内存限制字节数, CPU核数限制)。返回null表示无限制或获取失败</returns>
+    /// <remarks>
+    /// 读取 cgroup v1/v2 的资源限制配置:
+    /// - 内存限制:/sys/fs/cgroup/memory/memory.limit_in_bytes (v1) 或 /sys/fs/cgroup/memory.max (v2)
+    /// - CPU限制:/sys/fs/cgroup/cpu/cpu.cfs_quota_us 和 cpu.cfs_period_us (v1) 或 /sys/fs/cgroup/cpu.max (v2)
+    /// </remarks>
+    public static (Int64? MemoryLimit, Double? CpuLimit) GetContainerLimits()
+    {
+        Int64? memoryLimit = null;
+        Double? cpuLimit = null;
+
+        if (!Runtime.Linux) return (memoryLimit, cpuLimit);
+
+        // 尝试读取内存限制
+        // cgroup v2
+        var memFile = "/sys/fs/cgroup/memory.max";
+        if (File.Exists(memFile))
+        {
+            if (TryRead(memFile, out var value) && value != "max")
+                memoryLimit = value.ToLong();
+        }
+        else
+        {
+            // cgroup v1
+            memFile = "/sys/fs/cgroup/memory/memory.limit_in_bytes";
+            if (File.Exists(memFile) && TryRead(memFile, out var value))
+            {
+                var limit = value.ToLong();
+                // 如果接近系统最大值(通常是一个很大的数),则认为无限制
+                if (limit > 0 && limit < 9_000_000_000_000_000_000L)
+                    memoryLimit = limit;
+            }
+        }
+
+        // 尝试读取 CPU 限制
+        // cgroup v2: cpu.max 格式为 "quota period" 或 "max period"
+        var cpuFile = "/sys/fs/cgroup/cpu.max";
+        if (File.Exists(cpuFile))
+        {
+            if (TryRead(cpuFile, out var value))
+            {
+                var parts = value.Split(' ');
+                if (parts.Length >= 2 && parts[0] != "max")
+                {
+                    var quota = parts[0].ToLong();
+                    var period = parts[1].ToLong();
+                    if (quota > 0 && period > 0)
+                        cpuLimit = (Double)quota / period;
+                }
+            }
+        }
+        else
+        {
+            // cgroup v1
+            var quotaFile = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us";
+            var periodFile = "/sys/fs/cgroup/cpu/cpu.cfs_period_us";
+            if (File.Exists(quotaFile) && File.Exists(periodFile))
+            {
+                if (TryRead(quotaFile, out var quotaStr) && TryRead(periodFile, out var periodStr))
+                {
+                    var quota = quotaStr.ToLong();
+                    var period = periodStr.ToLong();
+                    // quota=-1 表示无限制
+                    if (quota > 0 && period > 0)
+                        cpuLimit = (Double)quota / period;
+                }
+            }
+        }
+
+        return (memoryLimit, cpuLimit);
+    }
+
+    /// <summary>获取容器内存使用量</summary>
+    /// <returns>当前内存使用量(字节)。获取失败返回null</returns>
+    public static Int64? GetContainerMemoryUsage()
+    {
+        if (!Runtime.Linux) return null;
+
+        // cgroup v2
+        var usageFile = "/sys/fs/cgroup/memory.current";
+        if (File.Exists(usageFile) && TryRead(usageFile, out var value))
+            return value.ToLong();
+
+        // cgroup v1
+        usageFile = "/sys/fs/cgroup/memory/memory.usage_in_bytes";
+        if (File.Exists(usageFile) && TryRead(usageFile, out value))
+            return value.ToLong();
+
+        return null;
+    }
+    #endregion
+}
Modified +3 -1128
diff --git a/NewLife.Core/Common/MachineInfo.cs b/NewLife.Core/Common/MachineInfo.cs
index 3966013..f8e8ea6 100644
--- a/NewLife.Core/Common/MachineInfo.cs
+++ b/NewLife.Core/Common/MachineInfo.cs
@@ -1,26 +1,13 @@
 using System.ComponentModel;
-using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.Net.NetworkInformation;
 using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Security;
 using NewLife.Collections;
+using NewLife.Data;
 using NewLife.Log;
 using NewLife.Model;
 using NewLife.Reflection;
 using NewLife.Serialization;
-using System.Runtime.Versioning;
-using System.Diagnostics.CodeAnalysis;
-using NewLife.Windows;
-using NewLife.Data;
-
-#if NETFRAMEWORK
-using System.Management;
-using Microsoft.VisualBasic.Devices;
-#endif
-#if NETFRAMEWORK || NET5_0_OR_GREATER
-using Microsoft.Win32;
-#endif
 
 namespace NewLife;
 
@@ -43,7 +30,7 @@ public interface IMachineInfo
 /// 
 /// 刷新信息成本较高,建议采用单例模式
 /// </remarks>
-public class MachineInfo : IExtend
+public partial class MachineInfo : IExtend
 {
     #region 属性
     /// <summary>系统名称</summary>
@@ -280,322 +267,6 @@ public class MachineInfo : IExtend
     /// <summary>裁剪不可见字符并去除两端空白</summary>
     private static String? Clean(String? value) => value.TrimInvisible()?.Trim();
 
-#if NET5_0_OR_GREATER
-    [SupportedOSPlatform("windows")]
-#endif
-    private void LoadWindowsInfo()
-    {
-        var str = "";
-
-        // 从注册表读取 MachineGuid
-#if NETFRAMEWORK || NET6_0_OR_GREATER
-        var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
-        if (reg != null) str = reg.GetValue("MachineGuid") + "";
-        if (str.IsNullOrEmpty())
-        {
-            reg = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
-            reg = reg?.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
-            if (reg != null) str = reg.GetValue("MachineGuid") + "";
-        }
-
-        if (!str.IsNullOrEmpty()) Guid = str;
-
-        reg = Registry.LocalMachine.OpenSubKey(@"SYSTEM\HardwareConfig");
-        if (reg != null)
-        {
-            str = (reg.GetValue("LastConfig") + "")?.Trim('{', '}').ToUpper();
-
-            // UUID取不到时返回 FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
-            if (!str.IsNullOrEmpty() && !str.EqualIgnoreCase("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) UUID = str;
-        }
-
-        reg = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS");
-        reg ??= Registry.LocalMachine.OpenSubKey(@"SYSTEM\HardwareConfig\Current");
-        if (reg != null)
-        {
-            Product = (reg.GetValue("SystemProductName") + "").Replace("System Product Name", null);
-            if (Product.IsNullOrEmpty()) Product = reg.GetValue("BaseBoardProduct") + "";
-
-            Vendor = reg.GetValue("SystemManufacturer") + "";
-            if (Vendor.IsNullOrEmpty()) Vendor = reg.GetValue("ASUSTeK COMPUTER INC.") + "";
-        }
-
-        reg = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
-        if (reg != null) Processor = reg.GetValue("ProcessorNameString") + "";
-
-        // 旧版系统(如win2008)没有UUID的注册表项,需要用wmic查询。也可能因为过去的某个BUG,导致GUID跟UUID相等
-        if (UUID.IsNullOrEmpty() || UUID == Guid || Vendor.IsNullOrEmpty())
-        {
-            var csproduct = ReadWmic("csproduct", "Name", "UUID", "Vendor");
-            if (csproduct != null)
-            {
-                if (csproduct.TryGetValue("Name", out str) && !str.IsNullOrEmpty() && Product.IsNullOrEmpty()) Product = str;
-                if (csproduct.TryGetValue("UUID", out str) && !str.IsNullOrEmpty()) UUID = str;
-                if (csproduct.TryGetValue("Vendor", out str) && !str.IsNullOrEmpty()) Vendor = str;
-            }
-        }
-#else
-        str = "reg".Execute(@"query HKLM\SOFTWARE\Microsoft\Cryptography /v MachineGuid", 0, false);
-        if (!str.IsNullOrEmpty() && str.Contains("REG_SZ")) Guid = str.Substring("REG_SZ", null).Trim();
-
-        var csproduct = ReadWmiComMulti("csproduct", "Name", "UUID", "Vendor");
-        if (csproduct != null)
-        {
-            if (csproduct.TryGetValue("Name", out str)) Product = str;
-            if (csproduct.TryGetValue("UUID", out str)) UUID = str;
-            if (csproduct.TryGetValue("Vendor", out str)) Vendor = str;
-        }
-#endif
-        // 获取内存大小
-#if NETFRAMEWORK || WINDOWS
-        {
-            var ci = new Microsoft.VisualBasic.Devices.ComputerInfo();
-            Memory = ci.TotalPhysicalMemory;
-        }
-#endif
-
-        // 获取操作系统名称和版本
-#if NETFRAMEWORK
-        try
-        {
-            var ci = new Microsoft.VisualBasic.Devices.ComputerInfo();
-
-            // 系统名取WMI可能出错
-            OSName = ci.OSFullName?.Replace("®", null).TrimPrefix("Microsoft").Trim();
-            OSVersion = ci.OSVersion;
-        }
-        catch
-        {
-            try
-            {
-                var reg2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
-                if (reg2 != null)
-                {
-                    OSName = reg2.GetValue("ProductName") + "";
-                    OSVersion = reg2.GetValue("ReleaseId") + "";
-                }
-            }
-            catch (Exception ex)
-            {
-                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
-            }
-        }
-        //#elif NET5_0_OR_GREATER
-        //        OSName = GetInfo("Win32_OperatingSystem", "Caption")?.TrimStart("Microsoft").Trim();
-        //        OSVersion = GetInfo("Win32_OperatingSystem", "Version");
-#else
-        var os = ReadWmiComMulti("os", "Caption", "Version");
-        if (os == null || os.Count == 0)
-        {
-            os = ReadPowerShell("Get-WmiObject Win32_OperatingSystem | Select-Object Caption, Version | ConvertTo-Json");
-        }
-        if (os is { Count: > 0 })
-        {
-            if (os.TryGetValue("Caption", out str)) OSName = str.TrimPrefix("Microsoft").Trim();
-            if (os.TryGetValue("Version", out str)) OSVersion = str;
-        }
-#endif
-
-#if NETFRAMEWORK
-        //Processor = GetInfo("Win32_Processor", "Name");
-        //CpuID = GetInfo("Win32_Processor", "ProcessorId");
-        //var uuid = GetInfo("Win32_ComputerSystemProduct", "UUID");
-        //Product = GetInfo("Win32_ComputerSystemProduct", "Name");
-        DiskID = GetInfo("Win32_DiskDrive where mediatype=\"Fixed hard disk media\"", "SerialNumber");
-
-        var sn = GetInfo("Win32_BIOS", "SerialNumber");
-        if (!sn.IsNullOrEmpty() && !sn.EqualIgnoreCase("System Serial Number")) Serial = sn;
-        Board = GetInfo("Win32_BaseBoard", "SerialNumber");
-#else
-        var diskStr = ReadWmiComSingle("diskdrive where mediatype=\"Fixed hard disk media\"", "serialnumber");
-        if (!diskStr.IsNullOrEmpty()) DiskID = diskStr?.Trim();
-
-        var sn = ReadWmiComSingle("bios", "serialnumber");
-        if (!sn.IsNullOrEmpty() && !sn.EqualIgnoreCase("System Serial Number")) Serial = sn?.Trim();
-
-        var boardStr = ReadWmiComSingle("baseboard", "serialnumber");
-        if (!boardStr.IsNullOrEmpty()) Board = boardStr?.Trim();
-
-
-        //// 不要在刷新里面取CPU负载,因为运行wmic会导致CPU负载很不准确,影响测量
-        //var cpu = ReadWmic("cpu", "Name", "ProcessorId", "LoadPercentage");
-        //if (cpu != null)
-        //{
-        //    if (cpu.TryGetValue("Name", out str)) Processor = str;
-        //    //if (cpu.TryGetValue("ProcessorId", out str)) CpuID = str;
-        //    if (cpu.TryGetValue("LoadPercentage", out str)) CpuRate = (Single)(str.ToDouble() / 100);
-        //}
-#endif
-
-#if !NETFRAMEWORK
-        if (OSName.IsNullOrEmpty())
-            OSName = RuntimeInformation.OSDescription.TrimPrefix("Microsoft").Trim();
-        if (OSVersion.IsNullOrEmpty())
-            OSVersion = Environment.OSVersion.Version.ToString();
-#endif
-    }
-
-#if NET5_0_OR_GREATER
-    [SupportedOSPlatform("linux")]
-#endif
-    private void LoadLinuxInfo()
-    {
-        var str = GetLinuxName();
-        if (!str.IsNullOrEmpty()) OSName = str;
-
-        var device = ReadDeviceInfo();
-
-        if (device.TryGetValue("Platform", out str))
-            OSName = str;
-        if (device.TryGetValue("Version", out str))
-            OSVersion = str;
-
-        // 树莓派的Hardware无法区分P0/P4
-        var dic = ReadInfo("/proc/cpuinfo");
-        if (dic != null)
-        {
-            if (dic.TryGetValue("Hardware", out str) ||
-                dic.TryGetValue("cpu model", out str) ||
-                dic.TryGetValue("model name", out str))
-                Processor = str?.TrimPrefix("vendor ");
-
-            if (device.TryGetValue("Product", out str))
-                Product = str;
-            else if (dic.TryGetValue("Model", out str))
-                Product = str;
-
-            if (dic.TryGetValue("vendor_id", out str))
-                Vendor = str;
-
-            //if (device.TryGetValue("Fingerprint", out str) && !str.IsNullOrEmpty())
-            //    CpuID = str;
-            if (dic.TryGetValue("Serial", out str) && !str.IsNullOrEmpty() && !str.Trim('0').IsNullOrEmpty())
-                UUID = str;
-        }
-
-        var mid = "/etc/machine-id";
-        if (!File.Exists(mid)) mid = "/var/lib/dbus/machine-id";
-        if (TryRead(mid, out var value))
-            Guid = value;
-        else if (device.TryGetValue("android_id", out str) && !str.IsNullOrEmpty() && str != "unknown")
-            Guid = str;
-        //else if (android.TryGetValue("Id", out str))
-        //    Guid = str;
-
-        // DMI信息位于 /sys/class/dmi/id/ 目录,可以直接读取,不需要执行dmidecode命令
-        var uuid = "";
-        var file = "/sys/class/dmi/id/product_uuid";
-        if (!File.Exists(file)) file = "/etc/uuid";
-        if (!File.Exists(file)) file = "/proc/serial_num";  // miui12支持/proc/serial_num
-        if (TryRead(file, out value))
-            uuid = value;
-        else if (device.TryGetValue("Serial", out str) && str != "unknown")
-            uuid = str;
-        if (!uuid.IsNullOrEmpty()) UUID = uuid;
-
-        // 从release文件读取产品
-        var prd = GetProductByRelease();
-        if (!prd.IsNullOrEmpty()) Product = prd;
-
-        if (prd.IsNullOrEmpty() && TryRead("/sys/class/dmi/id/product_name", out var product_name))
-        {
-            Product = product_name;
-
-            // 增加制造商。如 Tencent Cloud,它的产品名只有 CVM。阿里云产品名 Alibaba Cloud ECS
-            if (TryRead("/sys/class/dmi/id/sys_vendor", out var vendor) && !vendor.IsNullOrEmpty())
-            {
-                Vendor = vendor;
-
-                if (!product_name.IsNullOrEmpty() && !product_name.Contains(vendor))
-                {
-                    // 红帽KVM太流行,细化处理
-                    if (product_name == "KVM" && vendor == "Red Hat" &&
-                        TryRead("/sys/class/dmi/id/product_version", out var ver) && !ver.IsNullOrEmpty())
-                    {
-                        var p = ver.IndexOf('(');
-                        if (p > 0) ver = ver[..p].Trim();
-                        Product = ver;
-                    }
-                }
-            }
-        }
-
-        file = "/sys/class/dmi/id/product_serial";
-        if (TryRead(file, out value)) Serial = value;
-
-        // 在DMI信息内,没有太好的BoardID取值
-        file = "/sys/class/dmi/id/product_sku";
-        if (TryRead(file, out value) && !value.IsNullOrEmpty())
-            Board = value;
-        else
-        {
-            file = "/sys/class/dmi/id/product_family";
-            if (TryRead(file, out value)) Board = value;
-        }
-
-        // 在虚拟机中,uuid可能出现一个时间id和一个guid。
-        var disks = GetFiles("/dev/disk/by-uuid", false);
-        if (disks.Count > 0)
-        {
-            // 去掉时间id例如 2025-08-14-18-36-42-00,因为它随着时间在改变
-            disks = disks.Where(e => !e.IsNullOrEmpty() && (e.Length < 10 || e[4] != '-' || e[..10].ToDateTime().Year < 2000)).ToList();
-        }
-
-        if (disks.Count == 0)
-        {
-            // id中需要剔除QEMU,去掉virtio-前缀,例如 virtio-uf6ag3b49w6v4e9ldgcj
-            disks = GetFiles("/dev/disk/by-id", true);
-            disks = disks.Where(e => !e.IsNullOrEmpty() && !e.Contains("QEMU_")).Select(e => e.TrimPrefix("virtio-")).ToList();
-        }
-
-        if (disks.Count == 0) disks = GetFiles("/dev/disk/by-partuuid", true);
-        if (disks.Count > 0) DiskID = disks.Where(e => !e.IsNullOrEmpty()).Join(",");
-
-        // 从*-release文件读取产品信息,具有更高优先级
-        file = "/etc/os-release";
-        if (TryRead(file, out value))
-        {
-            var dic2 = value.SplitAsDictionary("=", Environment.NewLine, true);
-
-            if (dic2.TryGetValue("Vendor", out str)) Vendor = str;
-            if (dic2.TryGetValue("Product", out str)) Product = str;
-            if (dic2.TryGetValue("Serial", out str)) Serial = str;
-            if (dic2.TryGetValue("Board", out str)) Board = str;
-        }
-    }
-
-#if NET5_0_OR_GREATER
-    [SupportedOSPlatform("macos")]
-#endif
-    private void LoadMacInfo()
-    {
-        var dic = ReadCommand("sw_vers");
-        if (dic != null)
-        {
-            if (dic.TryGetValue("ProductName", out var str)) OSName = str;
-            if (dic.TryGetValue("ProductVersion", out str)) OSVersion = str;
-        }
-
-        dic = ReadCommand("system_profiler", "SPHardwareDataType");
-        if (dic != null)
-        {
-            //if (dic2.TryGetValue("Model Name", out str)) Product = str;
-            if (dic.TryGetValue("Model Identifier", out var str)) Product = str;
-            if (dic.TryGetValue("Processor Name", out str)) Processor = str;
-            if (dic.TryGetValue("Memory", out str)) Memory = (UInt64)str.TrimSuffix("GB").Trim().ToLong() * 1024 * 1024 * 1024;
-            if (dic.TryGetValue("Serial Number (system)", out str)) Serial = str;
-            if (dic.TryGetValue("Hardware UUID", out str)) UUID = str;
-        }
-
-        if (Vendor.IsNullOrEmpty()) Vendor = "Apple";
-
-        dic = ReadCommand("diskutil", "info disk1");
-        if (dic != null)
-        {
-            if (dic.TryGetValue("Disk / Partition UUID", out var str)) DiskID = str;
-        }
-    }
-
     private readonly ICollection<String> _excludes = [];
 
     /// <summary>获取实时数据,如CPU、内存、温度</summary>
@@ -612,208 +283,6 @@ public class MachineInfo : IExtend
         Provider?.Refresh(this);
     }
 
-    private void RefreshWindows()
-    {
-        MEMORYSTATUSEX ms = default;
-        ms.Init();
-        if (GlobalMemoryStatusEx(ref ms))
-        {
-            Memory = ms.ullTotalPhys;
-            AvailableMemory = ms.ullAvailPhys;
-            FreeMemory = ms.ullAvailPhys;
-        }
-
-        GetSystemTimes(out var idleTime, out var kernelTime, out var userTime);
-
-        var current = new SystemTime
-        {
-            IdleTime = idleTime.ToLong(),
-            TotalTime = kernelTime.ToLong() + userTime.ToLong(),
-        };
-
-        var idle = current.IdleTime - (_systemTime?.IdleTime ?? 0);
-        var total = current.TotalTime - (_systemTime?.TotalTime ?? 0);
-        _systemTime = current;
-
-        CpuRate = total == 0 ? 0 : Math.Round((Double)(total - idle) / total, 4);
-
-        var power = new PowerStatus();
-
-#if NETFRAMEWORK
-        if (!_excludes.Contains(nameof(Temperature)))
-        {
-            // 读取主板温度,不太准。标准方案是ring0通过IOPort读取CPU温度,太难在基础类库实现
-            var str = GetInfo("Win32_TemperatureProbe", "CurrentReading");
-            if (!str.IsNullOrEmpty())
-            {
-                Temperature = str.SplitAsInt().Average();
-            }
-            else
-            {
-                str = GetInfo("MSAcpi_ThermalZoneTemperature", "CurrentTemperature", "root/wmi");
-                if (!str.IsNullOrEmpty())
-                    Temperature = (str.SplitAsInt().Average() - 2732) / 10.0;
-                else
-                {
-                    if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Temperature信息无法读取");
-                    _excludes.Add(nameof(Temperature));
-                    Temperature = 0;
-                }
-            }
-        }
-
-        if (power.BatteryLifePercent > 0)
-            Battery = power.BatteryLifePercent;
-        else if (!_excludes.Contains(nameof(Battery)))
-        {
-            // 电池剩余
-            var str = GetInfo("Win32_Battery", "EstimatedChargeRemaining");
-            if (!str.IsNullOrEmpty())
-                Battery = str.SplitAsInt().Average() / 100.0;
-            else
-            {
-                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Battery信息无法读取");
-                _excludes.Add(nameof(Battery));
-                Battery = 0;
-            }
-        }
-#else
-        if (!_excludes.Contains(nameof(Temperature)))
-        {
-            var str = ReadWmiComSingle(@"/namespace:\\root\wmi path MSAcpi_ThermalZoneTemperature", "CurrentTemperature");
-            if (!str.IsNullOrEmpty())
-            {
-                Temperature = (str.SplitAsInt().Average() - 2732) / 10.0;
-            }
-            else
-            {
-                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Temperature信息无法读取");
-                _excludes.Add(nameof(Temperature));
-                Temperature = 0;
-            }
-        }
-
-        if (power.BatteryLifePercent > 0)
-            Battery = power.BatteryLifePercent;
-        else if (!_excludes.Contains(nameof(Battery)))
-        {
-            var str = ReadWmiComSingle("path win32_battery", "EstimatedChargeRemaining");
-            if (!str.IsNullOrEmpty())
-            {
-                Battery = str.SplitAsInt().Average() / 100.0;
-            }
-            else
-            {
-                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Battery信息无法读取");
-                _excludes.Add(nameof(Battery));
-                Battery = 0;
-            }
-        }
-#endif
-    }
-
-    private void RefreshLinux()
-    {
-        var dic = ReadInfo("/proc/meminfo");
-        if (dic != null)
-        {
-            if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
-                Memory = (UInt64)str.TrimSuffix(" kB").ToInt() * 1024;
-
-            // MemAvailable是系统内核预测的可用内存,过低则认为不能安全分配给新进程,可能过于悲观;
-            // MemFree是完全空闲的内存,未被使用的物理内存页,但内核不敢用;
-            static UInt64 GetMem(IDictionary<String, String?> mem, String key)
-            {
-                return mem.TryGetValue(key, out var v) && !v.IsNullOrEmpty() ? (UInt64)v.TrimSuffix(" kB").ToInt() * 1024 : 0;
-            }
-
-            var ma = GetMem(dic, "MemAvailable");
-            var mf = GetMem(dic, "MemFree");
-            var buffers = GetMem(dic, "Buffers");
-            var cached = GetMem(dic, "Cached");
-            var srecl = GetMem(dic, "SReclaimable");
-            var shmem = GetMem(dic, "Shmem");
-
-            AvailableMemory = ma;
-
-            // FreeMemory采用 free 命令的宽松口径:free + buffers + cache + SReclaimable - Shmem
-            var cache = cached + srecl;
-            if (cache > shmem) cache -= shmem;
-
-            FreeMemory = mf + buffers + cache;
-        }
-
-        // A2/A4温度获取,Buildroot,CPU温度和主板温度
-        if (TryRead("/sys/class/thermal/thermal_zone0/temp", out var value) ||
-            TryRead("/sys/class/thermal/thermal_zone1/temp", out value))
-        {
-            Temperature = value.ToDouble();
-            // 有时候温度会超过1000,可能是毫度。机器温度不会低于0度
-            if (Temperature > 1000) Temperature /= 1000;
-        }
-        // respberrypi + fedora
-        else if (TryRead("/sys/class/thermal/thermal_zone0/temp", out value) ||
-             TryRead("/sys/class/hwmon/hwmon0/temp1_input", out value) ||
-             TryRead("/sys/class/hwmon/hwmon0/temp2_input", out value) ||
-             TryRead("/sys/class/hwmon/hwmon0/device/hwmon/hwmon0/temp2_input", out value) ||
-             TryRead("/sys/devices/virtual/thermal/thermal_zone0/temp", out value))
-        {
-            Temperature = value.ToDouble() / 1000;
-        }
-        // A2温度获取,Ubuntu 16.04 LTS, Linux 3.4.39
-        else if (TryRead("/sys/class/hwmon/hwmon0/device/temp_value", out value))
-        {
-            if (!value.IsNullOrEmpty()) Temperature = value.Substring(null, ":").ToDouble();
-        }
-
-        // 电池剩余
-        if (TryRead("/sys/class/power_supply/BAT0/energy_now", out var energy_now) &&
-            TryRead("/sys/class/power_supply/BAT0/energy_full", out var energy_full))
-        {
-            Battery = energy_now.ToDouble() / energy_full.ToDouble();
-        }
-        else if (TryRead("/sys/class/power_supply/battery/capacity", out var capacity))
-        {
-            Battery = capacity.ToDouble() / 100.0;
-        }
-        else if (Runtime.Mono)
-        {
-            var battery = ReadDeviceBattery();
-            if (battery.TryGetValue("ChargeLevel", out var obj)) Battery = obj.ToDouble();
-        }
-
-        var file = "/proc/stat";
-        if (!_excludes.Contains(nameof(CpuRate)) && File.Exists(file))
-        {
-            // CPU指标:user,nice, system, idle, iowait, irq, softirq
-            // cpu  57057 0 14420 1554816 0 443 0 0 0 0
-            try
-            {
-                using var reader = new StreamReader(file);
-                var line = reader.ReadLine();
-                if (!line.IsNullOrEmpty() && line.StartsWith("cpu"))
-                {
-                    var vs = line.TrimPrefix("cpu").Trim().Split(' ');
-                    var current = new SystemTime
-                    {
-                        IdleTime = vs[3].ToLong(),
-                        TotalTime = vs.Take(7).Select(e => e.ToLong()).Sum().ToLong(),
-                    };
-
-                    var idle = current.IdleTime - (_systemTime?.IdleTime ?? 0);
-                    var total = current.TotalTime - (_systemTime?.TotalTime ?? 0);
-                    _systemTime = current;
-
-                    CpuRate = total == 0 ? 0 : Math.Round((Double)(total - idle) / total, 4);
-                }
-            }
-            catch
-            {
-                _excludes.Add(nameof(CpuRate));
-            }
-        }
-    }
-
     private Int64 _lastTime;
     private Int64 _lastSent;
     private Int64 _lastReceived;
@@ -859,57 +328,6 @@ public class MachineInfo : IExtend
     #endregion
 
     #region 辅助
-    /// <summary>获取Linux发行版名称</summary>
-    /// <returns>Linux发行版名称</returns>
-    public static String? GetLinuxName()
-    {
-        var fr = "/etc/redhat-release";
-        if (TryRead(fr, out var value)) return value;
-
-        var dr = "/etc/debian-release";
-        if (TryRead(dr, out value)) return value;
-
-        var sr = "/etc/os-release";
-        if (TryRead(sr, out value))
-        {
-            var dic = value.SplitAsDictionary("=", "\n", true);
-            if (dic.TryGetValue("PRETTY_NAME", out var pretty) && !pretty.IsNullOrEmpty()) return pretty.Trim();
-            if (dic.TryGetValue("NAME", out var name) && !name.IsNullOrEmpty()) return name.Trim();
-        }
-
-        var uname = "uname".Execute("-sr", 0, false)?.Trim();
-        if (!uname.IsNullOrEmpty())
-        {
-            // 支持Android系统名
-            var ss = uname.Split('-');
-            foreach (var item in ss)
-            {
-                if (!item.IsNullOrEmpty() && item.StartsWithIgnoreCase("Android")) return item;
-            }
-
-            return uname;
-        }
-
-        return null;
-    }
-
-    private static String? GetProductByRelease()
-    {
-        var di = "/etc/".AsDirectory();
-        if (!di.Exists) return null;
-
-        foreach (var fi in di.GetFiles("*-release"))
-        {
-            if (!fi.Name.EqualIgnoreCase("redhat-release", "debian-release", "os-release", "system-release"))
-            {
-                var dic = File.ReadAllText(fi.FullName).SplitAsDictionary("=", "\n", true);
-                if (dic.TryGetValue("BOARD", out var str)) return str;
-                if (dic.TryGetValue("BOARD_NAME", out str)) return str;
-            }
-        }
-
-        return null;
-    }
 
     private static Boolean TryRead(String fileName, [NotNullWhen(true)] out String? value)
     {
@@ -958,138 +376,6 @@ public class MachineInfo : IExtend
         return dic;
     }
 
-    private static IDictionary<String, String>? ReadCommand(String cmd, String? arguments = null)
-    {
-        var str = cmd.Execute(arguments, 0, false);
-        if (str.IsNullOrEmpty()) return null;
-
-        return str.SplitAsDictionary(":", "\n", true);
-    }
-
-    /// <summary>通过 PowerShell 命令读取信息</summary>
-    /// <param name="command">PowerShell命令</param>
-    /// <returns>解析后的字典</returns>
-    public static IDictionary<String, String> ReadPowerShell(String command)
-    {
-        var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
-
-        var args = $"-Command \"{command}\"";
-        var str = "powershell.exe".Execute(args, 3_000) ?? String.Empty;
-        if (!String.IsNullOrWhiteSpace(str))
-        {
-            foreach (var item in str.DecodeJson()!)
-            {
-                dic[item.Key] = item.Value?.ToString() ?? String.Empty;
-            }
-        }
-        return dic;
-    }
-
-    /// <summary>通过WMIC命令读取信息</summary>
-    /// <param name="type">WMI类型</param>
-    /// <param name="keys">查询字段</param>
-    /// <returns>解析后的字典</returns>
-    public static IDictionary<String, String> ReadWmic(String type, params String[] keys)
-    {
-        var dic = new Dictionary<String, IList<String>>(StringComparer.OrdinalIgnoreCase);
-        var dic2 = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
-
-        var args = $"{type} get {keys.Join(",")} /format:list";
-        var str = "wmic".Execute(args, 0, false)?.Trim();
-        if (str.IsNullOrEmpty()) return dic2;
-
-        var ss = str.Split("\r\n");
-        foreach (var item in ss)
-        {
-            var ks = item?.Split('=');
-            if (ks != null && ks.Length >= 2)
-            {
-                var k = ks[0].Trim();
-                var v = ks[1].Trim().TrimInvisible();
-                if (!k.IsNullOrEmpty() && !v.IsNullOrEmpty())
-                {
-                    if (!dic.TryGetValue(k, out var list))
-                        dic[k] = list = [];
-
-                    list.Add(v);
-                }
-            }
-        }
-
-        // 排序,避免多个磁盘序列号时,顺序变动
-        foreach (var item in dic)
-        {
-            dic2[item.Key] = item.Value.OrderBy(e => e).Join();
-        }
-
-        return dic2;
-    }
-
-    private static readonly Dictionary<String, String> _wmiAliases = new(StringComparer.OrdinalIgnoreCase)
-    {
-        { "csproduct", "Win32_ComputerSystemProduct" },
-        { "os", "Win32_OperatingSystem" },
-        { "diskdrive", "Win32_DiskDrive" },
-        { "bios", "Win32_BIOS" },
-        { "baseboard", "Win32_BaseBoard" },
-        { "win32_battery", "Win32_Battery" },
-    };
-
-    /// <summary>通过 WMI 查询多属性(自动选择 COM/wmic)。优先使用 COM 避免开进程</summary>
-    /// <param name="type">WMI 类型,支持 wmic 格式如 "csproduct"、"/namespace:\\root\wmi path MSAcpi_ThermalZoneTemperature"</param>
-    /// <param name="keys">查询字段</param>
-    /// <returns>属性字典</returns>
-    public static IDictionary<String, String> ReadWmiComMulti(String type, params String[] keys)
-    {
-        // 解析命名空间和类名
-        var nameSpace = "root\\cimv2";
-        var wmiClass = type;
-
-        if (type.StartsWith("/namespace:"))
-        {
-            var p = type.IndexOf(" path ", StringComparison.Ordinal);
-            if (p > 0)
-            {
-                nameSpace = type["/namespace:".Length..p].Trim('\\');
-                wmiClass = type[(p + 6)..].Trim();
-            }
-        }
-        else if (type.StartsWith("path "))
-        {
-            wmiClass = type[5..].Trim();
-        }
-
-        // 映射 wmic 别名到 WMI 类名
-        var alias = wmiClass;
-        var whereIdx = wmiClass.IndexOf(" where ", StringComparison.OrdinalIgnoreCase);
-        if (whereIdx > 0) alias = wmiClass[..whereIdx];
-
-        if (_wmiAliases.TryGetValue(alias, out var fullClass))
-        {
-            wmiClass = whereIdx > 0 ? $"{fullClass}{wmiClass[whereIdx..]}" : fullClass;
-        }
-
-        // 逐属性查询
-        var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
-        foreach (var key in keys)
-        {
-            var value = GetInfo(wmiClass, key, nameSpace);
-            if (!value.IsNullOrEmpty()) dic[key] = value;
-        }
-
-        return dic;
-    }
-
-    /// <summary>通过 WMI 查询单个属性(自动回退 wmic)。优先使用 COM 避免开进程</summary>
-    /// <param name="type">WMI 类型</param>
-    /// <param name="property">属性名</param>
-    /// <returns>属性值,失败返回 null</returns>
-    private static String? ReadWmiComSingle(String type, String property)
-    {
-        var dic = ReadWmiComMulti(type, property);
-        return dic.TryGetValue(property, out var v) && !v.IsNullOrEmpty() ? v : null;
-    }
-
     /// <summary>获取设备信息。用于Xamarin</summary>
     /// <returns>设备信息字典</returns>
     public static IDictionary<String, String?> ReadDeviceInfo()
@@ -1164,36 +450,6 @@ public class MachineInfo : IExtend
     }
     #endregion
 
-    #region 内存
-    [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
-    [SecurityCritical]
-    [return: MarshalAs(UnmanagedType.Bool)]
-    internal static extern Boolean GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
-
-    internal struct MEMORYSTATUSEX
-    {
-        internal UInt32 dwLength;
-
-        internal UInt32 dwMemoryLoad;
-
-        internal UInt64 ullTotalPhys;
-
-        internal UInt64 ullAvailPhys;
-
-        internal UInt64 ullTotalPageFile;
-
-        internal UInt64 ullAvailPageFile;
-
-        internal UInt64 ullTotalVirtual;
-
-        internal UInt64 ullAvailVirtual;
-
-        internal UInt64 ullAvailExtendedVirtual;
-
-        internal void Init() => dwLength = checked((UInt32)Marshal.SizeOf(typeof(MEMORYSTATUSEX)));
-    }
-    #endregion
-
     #region 磁盘
     /// <summary>获取指定目录所在盘可用空间,默认当前目录</summary>
     /// <param name="path">目录路径</param>
@@ -1251,159 +507,6 @@ public class MachineInfo : IExtend
     }
     #endregion
 
-    #region 容器检测
-    /// <summary>是否运行在容器中</summary>
-    /// <remarks>
-    /// 检测依据:
-    /// 1. 环境变量 DOTNET_RUNNING_IN_CONTAINER
-    /// 2. 存在 /.dockerenv 文件
-    /// 3. /proc/1/cgroup 包含 docker/kubepods/containerd 等关键字
-    /// </remarks>
-    public static Boolean IsInContainer
-    {
-        get
-        {
-            // 环境变量检测
-            var env = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER");
-            if (env.EqualIgnoreCase("true", "1")) return true;
-
-            if (!Runtime.Linux) return false;
-
-            // Docker 环境文件检测
-            if (File.Exists("/.dockerenv")) return true;
-
-            // cgroup 检测
-            var cgroupFile = "/proc/1/cgroup";
-            if (File.Exists(cgroupFile))
-            {
-                try
-                {
-                    var content = File.ReadAllText(cgroupFile);
-                    if (content.Contains("docker") ||
-                        content.Contains("kubepods") ||
-                        content.Contains("containerd") ||
-                        content.Contains("lxc"))
-                        return true;
-                }
-                catch { }
-            }
-
-            return false;
-        }
-    }
-
-    /// <summary>获取容器资源限制</summary>
-    /// <returns>元组:(内存限制字节数, CPU核数限制)。返回null表示无限制或获取失败</returns>
-    /// <remarks>
-    /// 读取 cgroup v1/v2 的资源限制配置:
-    /// - 内存限制:/sys/fs/cgroup/memory/memory.limit_in_bytes (v1) 或 /sys/fs/cgroup/memory.max (v2)
-    /// - CPU限制:/sys/fs/cgroup/cpu/cpu.cfs_quota_us 和 cpu.cfs_period_us (v1) 或 /sys/fs/cgroup/cpu.max (v2)
-    /// </remarks>
-    public static (Int64? MemoryLimit, Double? CpuLimit) GetContainerLimits()
-    {
-        Int64? memoryLimit = null;
-        Double? cpuLimit = null;
-
-        if (!Runtime.Linux) return (memoryLimit, cpuLimit);
-
-        // 尝试读取内存限制
-        // cgroup v2
-        var memFile = "/sys/fs/cgroup/memory.max";
-        if (File.Exists(memFile))
-        {
-            if (TryRead(memFile, out var value) && value != "max")
-                memoryLimit = value.ToLong();
-        }
-        else
-        {
-            // cgroup v1
-            memFile = "/sys/fs/cgroup/memory/memory.limit_in_bytes";
-            if (File.Exists(memFile) && TryRead(memFile, out var value))
-            {
-                var limit = value.ToLong();
-                // 如果接近系统最大值(通常是一个很大的数),则认为无限制
-                if (limit > 0 && limit < 9_000_000_000_000_000_000L)
-                    memoryLimit = limit;
-            }
-        }
-
-        // 尝试读取 CPU 限制
-        // cgroup v2: cpu.max 格式为 "quota period" 或 "max period"
-        var cpuFile = "/sys/fs/cgroup/cpu.max";
-        if (File.Exists(cpuFile))
-        {
-            if (TryRead(cpuFile, out var value))
-            {
-                var parts = value.Split(' ');
-                if (parts.Length >= 2 && parts[0] != "max")
-                {
-                    var quota = parts[0].ToLong();
-                    var period = parts[1].ToLong();
-                    if (quota > 0 && period > 0)
-                        cpuLimit = (Double)quota / period;
-                }
-            }
-        }
-        else
-        {
-            // cgroup v1
-            var quotaFile = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us";
-            var periodFile = "/sys/fs/cgroup/cpu/cpu.cfs_period_us";
-            if (File.Exists(quotaFile) && File.Exists(periodFile))
-            {
-                if (TryRead(quotaFile, out var quotaStr) && TryRead(periodFile, out var periodStr))
-                {
-                    var quota = quotaStr.ToLong();
-                    var period = periodStr.ToLong();
-                    // quota=-1 表示无限制
-                    if (quota > 0 && period > 0)
-                        cpuLimit = (Double)quota / period;
-                }
-            }
-        }
-
-        return (memoryLimit, cpuLimit);
-    }
-
-    /// <summary>获取容器内存使用量</summary>
-    /// <returns>当前内存使用量(字节)。获取失败返回null</returns>
-    public static Int64? GetContainerMemoryUsage()
-    {
-        if (!Runtime.Linux) return null;
-
-        // cgroup v2
-        var usageFile = "/sys/fs/cgroup/memory.current";
-        if (File.Exists(usageFile) && TryRead(usageFile, out var value))
-            return value.ToLong();
-
-        // cgroup v1
-        usageFile = "/sys/fs/cgroup/memory/memory.usage_in_bytes";
-        if (File.Exists(usageFile) && TryRead(usageFile, out value))
-            return value.ToLong();
-
-        return null;
-    }
-    #endregion
-
-    #region Windows辅助
-    [DllImport("kernel32.dll", SetLastError = true)]
-    private static extern Boolean GetSystemTimes(out FILETIME idleTime, out FILETIME kernelTime, out FILETIME userTime);
-
-    private struct FILETIME
-    {
-        public UInt32 Low;
-
-        public UInt32 High;
-
-        public FILETIME(Int64 time)
-        {
-            Low = (UInt32)time;
-            High = (UInt32)(time >> 32);
-        }
-
-        public Int64 ToLong() => (Int64)(((UInt64)High << 32) | Low);
-    }
-
     private class SystemTime
     {
         public Int64 IdleTime;
@@ -1411,232 +514,4 @@ public class MachineInfo : IExtend
     }
 
     private SystemTime? _systemTime;
-
-    /// <summary>获取WMI信息。NETFX直接用ManagementObjectSearcher;net*-windows反射System.Management并回退COM;其它运行时用COM并回退wmic</summary>
-    /// <param name="path">WMI路径</param>
-    /// <param name="property">属性名</param>
-    /// <param name="nameSpace">命名空间,默认 root\cimv2</param>
-    /// <returns>查询结果</returns>
-    public static String GetInfo(String path, String property, String? nameSpace = null)
-    {
-        // 规范化命名空间:WMI 要求反斜杠分隔,调用者可能传入 root/wmi 等正斜杠格式
-        if (nameSpace != null) nameSpace = nameSpace.Replace("/", "\\");
-
-#if NETFRAMEWORK
-        // Linux Mono不支持WMI
-        if (Runtime.Mono) return "";
-
-        var bbs = new List<String>();
-        try
-        {
-            var wql = $"Select {property} From {path}";
-            var cimobject = new ManagementObjectSearcher(nameSpace, wql);
-            var moc = cimobject.Get();
-            foreach (var mo in moc)
-            {
-                var val = mo?.Properties?[property]?.Value;
-                if (val != null)
-                {
-                    var v = val.ToString().TrimInvisible()?.Trim();
-                    if (v != null) bbs.Add(v);
-                }
-            }
-        }
-        catch (Exception ex)
-        {
-            if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("WMI.GetInfo({0})失败!{1}", path, ex.Message);
-            return "";
-        }
-
-        bbs.Sort();
-
-        return bbs.Distinct().Join();
-#else
-        var ns = nameSpace ?? "root\\cimv2";
-
-#if __WIN__
-        // net*-windows:优先反射 System.Management 走标准 WMI,失败回退 COM;两者均失败降级 wmic 兜底
-        {
-            var value = QueryWmiSystemManagement(path, property, ns);
-            if (value != null) return value;
-
-            value = QueryWmiCom(path, property, ns);
-            if (value != null) return value;
-
-            // 两种 WMI 路径均失败(如 WMI 服务异常),降级到 wmic 进程兜底
-            if (XTrace.Log.Level <= LogLevel.Debug)
-                XTrace.WriteLine("WMI 均失败,回退 wmic: {0}.{1}", path, property);
-        }
-#else
-        // 其它运行时(netstandard/net*非windows):优先 COM,失败回退 wmic
-#if NET5_0_OR_GREATER
-        if (OperatingSystem.IsWindows())
-#else
-        if (Runtime.Windows)
-#endif
-        {
-            var value = QueryWmiCom(path, property, ns);
-            if (value != null) return value;
-        }
-#endif
-
-        // wmic 兜底(__WIN__ 降级 + 其它运行时)。不带命名空间时也要用 path 前缀以支持完整类名
-        var type = !ns.EqualIgnoreCase("root\\cimv2") ? $"/namespace:\\\\{ns} path {path}" : $"path {path}";
-        var dic = ReadWmic(type, property);
-        return dic.TryGetValue(property, out var v) ? v : "";
-#endif
-    }
-
-#if !NETFRAMEWORK
-    /// <summary>通过反射 System.Management 查询 WMI 属性。用于 net*-windows,不新增编译期依赖,不开进程</summary>
-    /// <remarks>
-    /// System.Management.dll 随 Windows 桌面运行时提供(net*-windows),通过反射调用避免核心库直接引用该程序集。
-    /// 内部使用 ManagementObjectSearcher(nameSpace, wql).Get() 枚举结果并读取属性。
-    /// </remarks>
-    /// <param name="wmiClass">WMI 类名,如 Win32_OperatingSystem</param>
-    /// <param name="property">属性名</param>
-    /// <param name="nameSpace">命名空间,如 root\cimv2</param>
-    /// <param name="throwOnError">失败时是否抛出异常,默认false吞掉异常返回null</param>
-    /// <returns>查询结果,失败返回null</returns>
-#if NET5_0_OR_GREATER
-    [SupportedOSPlatform("windows")]
-#endif
-    internal static String? QueryWmiSystemManagement(String wmiClass, String property, String nameSpace, Boolean throwOnError = false)
-    {
-        try
-        {
-            // 反射加载 System.Management(Windows桌面运行时内置,无编译期引用)
-            var asm = Assembly.Load("System.Management");
-            var searcherType = asm.GetType("System.Management.ManagementObjectSearcher")!;
-            var moType = asm.GetType("System.Management.ManagementObject")!;
-            var enumType = asm.GetType("System.Management.ManagementObjectCollection")!;
-
-            // new ManagementObjectSearcher(nameSpace, wql)
-            var wql = $"SELECT {property} FROM {wmiClass}";
-            var searcher = Activator.CreateInstance(searcherType, nameSpace, wql);
-            if (searcher == null) return null;
-
-            // .Get() → ManagementObjectCollection
-            var getMethod = searcherType.GetMethod("Get", Type.EmptyTypes)!;
-            var results = getMethod.Invoke(searcher, null);
-            if (results == null) return null;
-
-            // foreach (ManagementObject obj in results)
-            var bbs = new List<String>();
-            var getEnumeratorMethod = enumType.GetMethod("GetEnumerator")!;
-            var enumerator = getEnumeratorMethod.Invoke(results, null);
-            var moveNextMethod = enumerator!.GetType().GetMethod("MoveNext")!;
-            var currentProperty = enumerator.GetType().GetProperty("Current")!;
-
-            var itemProp = moType.GetProperty("Item", [typeof(String)])!;
-            var getValueMethod = itemProp.GetGetMethod()!;
-
-            while ((Boolean)moveNextMethod.Invoke(enumerator, null)!)
-            {
-                var obj = currentProperty.GetValue(enumerator);
-                if (obj == null) continue;
-
-                var val = getValueMethod.Invoke(obj, [property]);
-                if (val != null)
-                {
-                    var v = val.ToString()?.TrimInvisible()?.Trim();
-                    if (!v.IsNullOrEmpty()) bbs.Add(v);
-                }
-            }
-
-            // 清理 COM 引用
-            if (results is IDisposable d1) d1.Dispose();
-            if (searcher is IDisposable d2) d2.Dispose();
-
-            if (bbs.Count > 0)
-            {
-                bbs.Sort();
-                return bbs.Distinct().Join();
-            }
-        }
-        catch (Exception ex)
-        {
-            if (throwOnError) throw;
-            if (XTrace.Log.Level <= LogLevel.Debug)
-            {
-                XTrace.WriteLine("QueryWmiSystemManagement 失败: {0}.{1} @ {2}", wmiClass, property, nameSpace);
-                XTrace.WriteException(ex is TargetInvocationException tie ? tie.InnerException! : ex);
-            }
-        }
-
-        return null;
-    }
-
-    /// <summary>通过 COM 查询 WMI 属性。使用 Windows 内置 winmgmts 组件,不新增依赖,不开进程</summary>
-    /// <remarks>
-    /// 通过 winmgmts moniker 绑定 SWbemServices,执行 WQL 查询并枚举结果,SWbemObject 按名暴露 WMI 属性。
-    /// 相比 System.Management 无需该程序集,适用于 netstandard/net*非windows 运行时下的 Windows 主机。
-    /// COM 查询在部分环境可能失败,此时调用方应回退 wmic。
-    /// </remarks>
-    /// <param name="wmiClass">WMI 类名,如 Win32_OperatingSystem</param>
-    /// <param name="property">属性名</param>
-    /// <param name="nameSpace">命名空间,如 root\cimv2</param>
-    /// <param name="throwOnError">失败时是否抛出异常,默认false吞掉异常返回null</param>
-    /// <returns>查询结果,失败返回null</returns>
-#if NET5_0_OR_GREATER
-    [SupportedOSPlatform("windows")]
-#endif
-    internal static String? QueryWmiCom(String wmiClass, String property, String nameSpace, Boolean throwOnError = false)
-    {
-        Object? services = null;
-        Object? results = null;
-        try
-        {
-            // 通过 winmgmts moniker 绑定 WMI 服务,规避 SWbemLocator.ConnectServer 的 IDispatch 参数编组问题
-            var moniker = $"winmgmts:\\\\.\\{nameSpace}";
-            services = Marshal.BindToMoniker(moniker);
-            if (services == null) return null;
-
-            // ExecQuery 执行 WQL 查询,返回 SWbemObjectSet
-            var wql = $"SELECT {property} FROM {wmiClass}";
-            results = services.GetType().InvokeMember("ExecQuery", BindingFlags.InvokeMethod, null, services, new Object[] { wql });
-            if (results == null) return null;
-
-            // SWbemObjectSet 实现 IEnumVARIANT,可直接 foreach;SWbemObject 通过 IDispatch 按名暴露 WMI 属性
-            var bbs = new List<String>();
-            foreach (var obj in (System.Collections.IEnumerable)results)
-            {
-                if (obj == null) continue;
-                try
-                {
-                    var val = obj.GetType().InvokeMember(property, BindingFlags.GetProperty, null, obj, null);
-                    if (val != null)
-                    {
-                        var v = val.ToString()?.TrimInvisible()?.Trim();
-                        if (!v.IsNullOrEmpty()) bbs.Add(v);
-                    }
-                }
-                finally { try { Marshal.FinalReleaseComObject(obj); } catch { } }
-            }
-
-            if (bbs.Count > 0)
-            {
-                bbs.Sort();
-                return bbs.Distinct().Join();
-            }
-        }
-        catch (Exception ex)
-        {
-            if (throwOnError) throw;
-            if (XTrace.Log.Level <= LogLevel.Debug)
-            {
-                XTrace.WriteLine("QueryWmiCom 失败: {0}.{1} @ {2}", wmiClass, property, nameSpace);
-                XTrace.WriteException(ex is TargetInvocationException tie ? tie.InnerException! : ex);
-            }
-        }
-        finally
-        {
-            if (results != null) try { Marshal.FinalReleaseComObject(results); } catch { }
-            if (services != null) try { Marshal.FinalReleaseComObject(services); } catch { }
-        }
-
-        return null;
-    }
-#endif
-    #endregion
 }
\ No newline at end of file
Added +292 -0
diff --git a/NewLife.Core/Common/MachineInfo.Linux.cs b/NewLife.Core/Common/MachineInfo.Linux.cs
new file mode 100644
index 0000000..fa11342
--- /dev/null
+++ b/NewLife.Core/Common/MachineInfo.Linux.cs
@@ -0,0 +1,292 @@
+using System.Runtime.Versioning;
+
+namespace NewLife;
+
+/// <summary>机器信息 Linux 部分</summary>
+public partial class MachineInfo
+{
+#if NET5_0_OR_GREATER
+    [SupportedOSPlatform("linux")]
+#endif
+    private void LoadLinuxInfo()
+    {
+        var str = GetLinuxName();
+        if (!str.IsNullOrEmpty()) OSName = str;
+
+        var device = ReadDeviceInfo();
+
+        if (device.TryGetValue("Platform", out str))
+            OSName = str;
+        if (device.TryGetValue("Version", out str))
+            OSVersion = str;
+
+        // 树莓派的Hardware无法区分P0/P4
+        var dic = ReadInfo("/proc/cpuinfo");
+        if (dic != null)
+        {
+            if (dic.TryGetValue("Hardware", out str) ||
+                dic.TryGetValue("cpu model", out str) ||
+                dic.TryGetValue("model name", out str))
+                Processor = str?.TrimPrefix("vendor ");
+
+            if (device.TryGetValue("Product", out str))
+                Product = str;
+            else if (dic.TryGetValue("Model", out str))
+                Product = str;
+
+            if (dic.TryGetValue("vendor_id", out str))
+                Vendor = str;
+
+            //if (device.TryGetValue("Fingerprint", out str) && !str.IsNullOrEmpty())
+            //    CpuID = str;
+            if (dic.TryGetValue("Serial", out str) && !str.IsNullOrEmpty() && !str.Trim('0').IsNullOrEmpty())
+                UUID = str;
+        }
+
+        var mid = "/etc/machine-id";
+        if (!File.Exists(mid)) mid = "/var/lib/dbus/machine-id";
+        if (TryRead(mid, out var value))
+            Guid = value;
+        else if (device.TryGetValue("android_id", out str) && !str.IsNullOrEmpty() && str != "unknown")
+            Guid = str;
+        //else if (android.TryGetValue("Id", out str))
+        //    Guid = str;
+
+        // DMI信息位于 /sys/class/dmi/id/ 目录,可以直接读取,不需要执行dmidecode命令
+        var uuid = "";
+        var file = "/sys/class/dmi/id/product_uuid";
+        if (!File.Exists(file)) file = "/etc/uuid";
+        if (!File.Exists(file)) file = "/proc/serial_num";  // miui12支持/proc/serial_num
+        if (TryRead(file, out value))
+            uuid = value;
+        else if (device.TryGetValue("Serial", out str) && str != "unknown")
+            uuid = str;
+        if (!uuid.IsNullOrEmpty()) UUID = uuid;
+
+        // 从release文件读取产品
+        var prd = GetProductByRelease();
+        if (!prd.IsNullOrEmpty()) Product = prd;
+
+        if (prd.IsNullOrEmpty() && TryRead("/sys/class/dmi/id/product_name", out var product_name))
+        {
+            Product = product_name;
+
+            // 增加制造商。如 Tencent Cloud,它的产品名只有 CVM。阿里云产品名 Alibaba Cloud ECS
+            if (TryRead("/sys/class/dmi/id/sys_vendor", out var vendor) && !vendor.IsNullOrEmpty())
+            {
+                Vendor = vendor;
+
+                if (!product_name.IsNullOrEmpty() && !product_name.Contains(vendor))
+                {
+                    // 红帽KVM太流行,细化处理
+                    if (product_name == "KVM" && vendor == "Red Hat" &&
+                        TryRead("/sys/class/dmi/id/product_version", out var ver) && !ver.IsNullOrEmpty())
+                    {
+                        var p = ver.IndexOf('(');
+                        if (p > 0) ver = ver[..p].Trim();
+                        Product = ver;
+                    }
+                }
+            }
+        }
+
+        file = "/sys/class/dmi/id/product_serial";
+        if (TryRead(file, out value)) Serial = value;
+
+        // 在DMI信息内,没有太好的BoardID取值
+        file = "/sys/class/dmi/id/product_sku";
+        if (TryRead(file, out value) && !value.IsNullOrEmpty())
+            Board = value;
+        else
+        {
+            file = "/sys/class/dmi/id/product_family";
+            if (TryRead(file, out value)) Board = value;
+        }
+
+        // 在虚拟机中,uuid可能出现一个时间id和一个guid。
+        var disks = GetFiles("/dev/disk/by-uuid", false);
+        if (disks.Count > 0)
+        {
+            // 去掉时间id例如 2025-08-14-18-36-42-00,因为它随着时间在改变
+            disks = disks.Where(e => !e.IsNullOrEmpty() && (e.Length < 10 || e[4] != '-' || e[..10].ToDateTime().Year < 2000)).ToList();
+        }
+
+        if (disks.Count == 0)
+        {
+            // id中需要剔除QEMU,去掉virtio-前缀,例如 virtio-uf6ag3b49w6v4e9ldgcj
+            disks = GetFiles("/dev/disk/by-id", true);
+            disks = disks.Where(e => !e.IsNullOrEmpty() && !e.Contains("QEMU_")).Select(e => e.TrimPrefix("virtio-")).ToList();
+        }
+
+        if (disks.Count == 0) disks = GetFiles("/dev/disk/by-partuuid", true);
+        if (disks.Count > 0) DiskID = disks.Where(e => !e.IsNullOrEmpty()).Join(",");
+
+        // 从*-release文件读取产品信息,具有更高优先级
+        file = "/etc/os-release";
+        if (TryRead(file, out value))
+        {
+            var dic2 = value.SplitAsDictionary("=", Environment.NewLine, true);
+
+            if (dic2.TryGetValue("Vendor", out str)) Vendor = str;
+            if (dic2.TryGetValue("Product", out str)) Product = str;
+            if (dic2.TryGetValue("Serial", out str)) Serial = str;
+            if (dic2.TryGetValue("Board", out str)) Board = str;
+        }
+    }
+
+    private void RefreshLinux()
+    {
+        var dic = ReadInfo("/proc/meminfo");
+        if (dic != null)
+        {
+            if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
+                Memory = (UInt64)str.TrimSuffix(" kB").ToInt() * 1024;
+
+            // MemAvailable是系统内核预测的可用内存,过低则认为不能安全分配给新进程,可能过于悲观;
+            // MemFree是完全空闲的内存,未被使用的物理内存页,但内核不敢用;
+            static UInt64 GetMem(IDictionary<String, String?> mem, String key)
+            {
+                return mem.TryGetValue(key, out var v) && !v.IsNullOrEmpty() ? (UInt64)v.TrimSuffix(" kB").ToInt() * 1024 : 0;
+            }
+
+            var ma = GetMem(dic, "MemAvailable");
+            var mf = GetMem(dic, "MemFree");
+            var buffers = GetMem(dic, "Buffers");
+            var cached = GetMem(dic, "Cached");
+            var srecl = GetMem(dic, "SReclaimable");
+            var shmem = GetMem(dic, "Shmem");
+
+            AvailableMemory = ma;
+
+            // FreeMemory采用 free 命令的宽松口径:free + buffers + cache + SReclaimable - Shmem
+            var cache = cached + srecl;
+            if (cache > shmem) cache -= shmem;
+
+            FreeMemory = mf + buffers + cache;
+        }
+
+        // A2/A4温度获取,Buildroot,CPU温度和主板温度
+        if (TryRead("/sys/class/thermal/thermal_zone0/temp", out var value) ||
+            TryRead("/sys/class/thermal/thermal_zone1/temp", out value))
+        {
+            Temperature = value.ToDouble();
+            // 有时候温度会超过1000,可能是毫度。机器温度不会低于0度
+            if (Temperature > 1000) Temperature /= 1000;
+        }
+        // respberrypi + fedora
+        else if (TryRead("/sys/class/thermal/thermal_zone0/temp", out value) ||
+             TryRead("/sys/class/hwmon/hwmon0/temp1_input", out value) ||
+             TryRead("/sys/class/hwmon/hwmon0/temp2_input", out value) ||
+             TryRead("/sys/class/hwmon/hwmon0/device/hwmon/hwmon0/temp2_input", out value) ||
+             TryRead("/sys/devices/virtual/thermal/thermal_zone0/temp", out value))
+        {
+            Temperature = value.ToDouble() / 1000;
+        }
+        // A2温度获取,Ubuntu 16.04 LTS, Linux 3.4.39
+        else if (TryRead("/sys/class/hwmon/hwmon0/device/temp_value", out value))
+        {
+            if (!value.IsNullOrEmpty()) Temperature = value.Substring(null, ":").ToDouble();
+        }
+
+        // 电池剩余
+        if (TryRead("/sys/class/power_supply/BAT0/energy_now", out var energy_now) &&
+            TryRead("/sys/class/power_supply/BAT0/energy_full", out var energy_full))
+        {
+            Battery = energy_now.ToDouble() / energy_full.ToDouble();
+        }
+        else if (TryRead("/sys/class/power_supply/battery/capacity", out var capacity))
+        {
+            Battery = capacity.ToDouble() / 100.0;
+        }
+        else if (Runtime.Mono)
+        {
+            var battery = ReadDeviceBattery();
+            if (battery.TryGetValue("ChargeLevel", out var obj)) Battery = obj.ToDouble();
+        }
+
+        var file = "/proc/stat";
+        if (!_excludes.Contains(nameof(CpuRate)) && File.Exists(file))
+        {
+            // CPU指标:user,nice, system, idle, iowait, irq, softirq
+            // cpu  57057 0 14420 1554816 0 443 0 0 0 0
+            try
+            {
+                using var reader = new StreamReader(file);
+                var line = reader.ReadLine();
+                if (!line.IsNullOrEmpty() && line.StartsWith("cpu"))
+                {
+                    var vs = line.TrimPrefix("cpu").Trim().Split(' ');
+                    var current = new SystemTime
+                    {
+                        IdleTime = vs[3].ToLong(),
+                        TotalTime = vs.Take(7).Select(e => e.ToLong()).Sum().ToLong(),
+                    };
+
+                    var idle = current.IdleTime - (_systemTime?.IdleTime ?? 0);
+                    var total = current.TotalTime - (_systemTime?.TotalTime ?? 0);
+                    _systemTime = current;
+
+                    CpuRate = total == 0 ? 0 : Math.Round((Double)(total - idle) / total, 4);
+                }
+            }
+            catch
+            {
+                _excludes.Add(nameof(CpuRate));
+            }
+        }
+    }
+
+    #region Linux辅助
+    /// <summary>获取Linux发行版名称</summary>
+    /// <returns>Linux发行版名称</returns>
+    public static String? GetLinuxName()
+    {
+        var fr = "/etc/redhat-release";
+        if (TryRead(fr, out var value)) return value;
+
+        var dr = "/etc/debian-release";
+        if (TryRead(dr, out value)) return value;
+
+        var sr = "/etc/os-release";
+        if (TryRead(sr, out value))
+        {
+            var dic = value.SplitAsDictionary("=", "\n", true);
+            if (dic.TryGetValue("PRETTY_NAME", out var pretty) && !pretty.IsNullOrEmpty()) return pretty.Trim();
+            if (dic.TryGetValue("NAME", out var name) && !name.IsNullOrEmpty()) return name.Trim();
+        }
+
+        var uname = "uname".Execute("-sr", 0, false)?.Trim();
+        if (!uname.IsNullOrEmpty())
+        {
+            // 支持Android系统名
+            var ss = uname.Split('-');
+            foreach (var item in ss)
+            {
+                if (!item.IsNullOrEmpty() && item.StartsWithIgnoreCase("Android")) return item;
+            }
+
+            return uname;
+        }
+
+        return null;
+    }
+
+    private static String? GetProductByRelease()
+    {
+        var di = "/etc/".AsDirectory();
+        if (!di.Exists) return null;
+
+        foreach (var fi in di.GetFiles("*-release"))
+        {
+            if (!fi.Name.EqualIgnoreCase("redhat-release", "debian-release", "os-release", "system-release"))
+            {
+                var dic = File.ReadAllText(fi.FullName).SplitAsDictionary("=", "\n", true);
+                if (dic.TryGetValue("BOARD", out var str)) return str;
+                if (dic.TryGetValue("BOARD_NAME", out str)) return str;
+            }
+        }
+
+        return null;
+    }
+    #endregion
+}
Added +49 -0
diff --git a/NewLife.Core/Common/MachineInfo.MacOS.cs b/NewLife.Core/Common/MachineInfo.MacOS.cs
new file mode 100644
index 0000000..c9b5b36
--- /dev/null
+++ b/NewLife.Core/Common/MachineInfo.MacOS.cs
@@ -0,0 +1,49 @@
+using System.Runtime.Versioning;
+
+namespace NewLife;
+
+/// <summary>机器信息 macOS 部分</summary>
+public partial class MachineInfo
+{
+#if NET5_0_OR_GREATER
+    [SupportedOSPlatform("macos")]
+#endif
+    private void LoadMacInfo()
+    {
+        var dic = ReadCommand("sw_vers");
+        if (dic != null)
+        {
+            if (dic.TryGetValue("ProductName", out var str)) OSName = str;
+            if (dic.TryGetValue("ProductVersion", out str)) OSVersion = str;
+        }
+
+        dic = ReadCommand("system_profiler", "SPHardwareDataType");
+        if (dic != null)
+        {
+            //if (dic2.TryGetValue("Model Name", out str)) Product = str;
+            if (dic.TryGetValue("Model Identifier", out var str)) Product = str;
+            if (dic.TryGetValue("Processor Name", out str)) Processor = str;
+            if (dic.TryGetValue("Memory", out str)) Memory = (UInt64)str.TrimSuffix("GB").Trim().ToLong() * 1024 * 1024 * 1024;
+            if (dic.TryGetValue("Serial Number (system)", out str)) Serial = str;
+            if (dic.TryGetValue("Hardware UUID", out str)) UUID = str;
+        }
+
+        if (Vendor.IsNullOrEmpty()) Vendor = "Apple";
+
+        dic = ReadCommand("diskutil", "info disk1");
+        if (dic != null)
+        {
+            if (dic.TryGetValue("Disk / Partition UUID", out var str)) DiskID = str;
+        }
+    }
+
+    #region macOS辅助
+    private static IDictionary<String, String>? ReadCommand(String cmd, String? arguments = null)
+    {
+        var str = cmd.Execute(arguments, 0, false);
+        if (str.IsNullOrEmpty()) return null;
+
+        return str.SplitAsDictionary(":", "\n", true);
+    }
+    #endregion
+}
Added +565 -0
diff --git a/NewLife.Core/Common/MachineInfo.Windows.cs b/NewLife.Core/Common/MachineInfo.Windows.cs
new file mode 100644
index 0000000..887c99a
--- /dev/null
+++ b/NewLife.Core/Common/MachineInfo.Windows.cs
@@ -0,0 +1,565 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security;
+using NewLife.Log;
+using NewLife.Windows;
+using NewLife.Serialization;
+using System.Runtime.Versioning;
+
+#if NETFRAMEWORK || NET5_0_OR_GREATER
+using Microsoft.Win32;
+#endif
+
+namespace NewLife;
+
+/// <summary>机器信息 Windows 部分</summary>
+public partial class MachineInfo
+{
+    #region 公共基础设施
+    [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+    [SecurityCritical]
+    [return: MarshalAs(UnmanagedType.Bool)]
+    internal static extern Boolean GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
+
+    internal struct MEMORYSTATUSEX
+    {
+        internal UInt32 dwLength;
+
+        internal UInt32 dwMemoryLoad;
+
+        internal UInt64 ullTotalPhys;
+
+        internal UInt64 ullAvailPhys;
+
+        internal UInt64 ullTotalPageFile;
+
+        internal UInt64 ullAvailPageFile;
+
+        internal UInt64 ullTotalVirtual;
+
+        internal UInt64 ullAvailVirtual;
+
+        internal UInt64 ullAvailExtendedVirtual;
+
+        internal void Init() => dwLength = checked((UInt32)Marshal.SizeOf(typeof(MEMORYSTATUSEX)));
+    }
+
+    [DllImport("kernel32.dll", SetLastError = true)]
+    private static extern Boolean GetSystemTimes(out FILETIME idleTime, out FILETIME kernelTime, out FILETIME userTime);
+
+    private struct FILETIME(Int64 time)
+    {
+        public UInt32 Low = (UInt32)time;
+
+        public UInt32 High = (UInt32)(time >> 32);
+
+        public readonly Int64 ToLong() => (Int64)(((UInt64)High << 32) | Low);
+    }
+
+    /// <summary>刷新内存和CPU数据(Windows/Linux共用 SystemTime)</summary>
+    private void RefreshMemoryAndCpu()
+    {
+        MEMORYSTATUSEX ms = default;
+        ms.Init();
+        if (GlobalMemoryStatusEx(ref ms))
+        {
+            Memory = ms.ullTotalPhys;
+            AvailableMemory = ms.ullAvailPhys;
+            FreeMemory = ms.ullAvailPhys;
+        }
+
+        GetSystemTimes(out var idleTime, out var kernelTime, out var userTime);
+
+        var current = new SystemTime
+        {
+            IdleTime = idleTime.ToLong(),
+            TotalTime = kernelTime.ToLong() + userTime.ToLong(),
+        };
+
+        var idle = current.IdleTime - (_systemTime?.IdleTime ?? 0);
+        var total = current.TotalTime - (_systemTime?.TotalTime ?? 0);
+        _systemTime = current;
+
+        CpuRate = total == 0 ? 0 : Math.Round((Double)(total - idle) / total, 4);
+    }
+
+#if NETFRAMEWORK || NET6_0_OR_GREATER
+    /// <summary>从注册表读取硬件信息(GUID/UUID/Vendor/Product/Processor)并做 csproduct 兜底</summary>
+    private void LoadHardwareFromRegistry()
+    {
+        var str = "";
+
+        var reg = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
+        if (reg != null) str = reg.GetValue("MachineGuid") + "";
+        if (str.IsNullOrEmpty())
+        {
+            reg = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
+            reg = reg?.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
+            if (reg != null) str = reg.GetValue("MachineGuid") + "";
+        }
+
+        if (!str.IsNullOrEmpty()) Guid = str;
+
+        reg = Registry.LocalMachine.OpenSubKey(@"SYSTEM\HardwareConfig");
+        if (reg != null)
+        {
+            str = (reg.GetValue("LastConfig") + "")?.Trim('{', '}').ToUpper();
+
+            // UUID取不到时返回 FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
+            if (!str.IsNullOrEmpty() && !str.EqualIgnoreCase("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) UUID = str;
+        }
+
+        reg = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS");
+        reg ??= Registry.LocalMachine.OpenSubKey(@"SYSTEM\HardwareConfig\Current");
+        if (reg != null)
+        {
+            Product = (reg.GetValue("SystemProductName") + "").Replace("System Product Name", null);
+            if (Product.IsNullOrEmpty()) Product = reg.GetValue("BaseBoardProduct") + "";
+
+            Vendor = reg.GetValue("SystemManufacturer") + "";
+            if (Vendor.IsNullOrEmpty()) Vendor = reg.GetValue("ASUSTeK COMPUTER INC.") + "";
+        }
+
+        reg = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
+        if (reg != null) Processor = reg.GetValue("ProcessorNameString") + "";
+
+        // 旧版系统(如win2008)没有UUID的注册表项,需要用wmic查询。也可能因为过去的某个BUG,导致GUID跟UUID相等
+        if (UUID.IsNullOrEmpty() || UUID == Guid || Vendor.IsNullOrEmpty())
+        {
+            var csproduct = ReadWmic("csproduct", "Name", "UUID", "Vendor");
+            if (csproduct != null)
+            {
+                if (csproduct.TryGetValue("Name", out str) && !str.IsNullOrEmpty() && Product.IsNullOrEmpty()) Product = str;
+                if (csproduct.TryGetValue("UUID", out str) && !str.IsNullOrEmpty()) UUID = str;
+                if (csproduct.TryGetValue("Vendor", out str) && !str.IsNullOrEmpty()) Vendor = str;
+            }
+        }
+    }
+#endif
+    #endregion
+
+    #region Windows WMI辅助
+    /// <summary>通过 PowerShell 命令读取信息</summary>
+    /// <param name="command">PowerShell命令</param>
+    /// <returns>解析后的字典</returns>
+    public static IDictionary<String, String> ReadPowerShell(String command)
+    {
+        var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
+
+        var args = $"-Command \"{command}\"";
+        var str = "powershell.exe".Execute(args, 3_000) ?? String.Empty;
+        if (!String.IsNullOrWhiteSpace(str))
+        {
+            foreach (var item in str.DecodeJson()!)
+            {
+                dic[item.Key] = item.Value?.ToString() ?? String.Empty;
+            }
+        }
+        return dic;
+    }
+
+    /// <summary>通过WMIC命令读取信息</summary>
+    /// <param name="type">WMI类型</param>
+    /// <param name="keys">查询字段</param>
+    /// <returns>解析后的字典</returns>
+    public static IDictionary<String, String> ReadWmic(String type, params String[] keys)
+    {
+        var dic = new Dictionary<String, IList<String>>(StringComparer.OrdinalIgnoreCase);
+        var dic2 = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
+
+        var args = $"{type} get {keys.Join(",")} /format:list";
+        var str = "wmic".Execute(args, 0, false)?.Trim();
+        if (str.IsNullOrEmpty()) return dic2;
+
+        var ss = str.Split("\r\n");
+        foreach (var item in ss)
+        {
+            var ks = item?.Split('=');
+            if (ks != null && ks.Length >= 2)
+            {
+                var k = ks[0].Trim();
+                var v = ks[1].Trim().TrimInvisible();
+                if (!k.IsNullOrEmpty() && !v.IsNullOrEmpty())
+                {
+                    if (!dic.TryGetValue(k, out var list))
+                        dic[k] = list = [];
+
+                    list.Add(v);
+                }
+            }
+        }
+
+        // 排序,避免多个磁盘序列号时,顺序变动
+        foreach (var item in dic)
+        {
+            dic2[item.Key] = item.Value.OrderBy(e => e).Join();
+        }
+
+        return dic2;
+    }
+
+    private static readonly Dictionary<String, String> _wmiAliases = new(StringComparer.OrdinalIgnoreCase)
+    {
+        { "csproduct", "Win32_ComputerSystemProduct" },
+        { "os", "Win32_OperatingSystem" },
+        { "diskdrive", "Win32_DiskDrive" },
+        { "bios", "Win32_BIOS" },
+        { "baseboard", "Win32_BaseBoard" },
+        { "win32_battery", "Win32_Battery" },
+    };
+
+    /// <summary>通过 WMI 查询多属性(自动选择 COM/wmic)。优先使用 COM 避免开进程</summary>
+    /// <param name="type">WMI 类型,支持 wmic 格式如 "csproduct"、"/namespace:\\root\wmi path MSAcpi_ThermalZoneTemperature"</param>
+    /// <param name="keys">查询字段</param>
+    /// <returns>属性字典</returns>
+    public static IDictionary<String, String> ReadWmiComMulti(String type, params String[] keys)
+    {
+        // 解析命名空间和类名
+        var nameSpace = "root\\cimv2";
+        var wmiClass = type;
+
+        if (type.StartsWith("/namespace:"))
+        {
+            var p = type.IndexOf(" path ", StringComparison.Ordinal);
+            if (p > 0)
+            {
+                nameSpace = type["/namespace:".Length..p].Trim('\\');
+                wmiClass = type[(p + 6)..].Trim();
+            }
+        }
+        else if (type.StartsWith("path "))
+        {
+            wmiClass = type[5..].Trim();
+        }
+
+        // 映射 wmic 别名到 WMI 类名
+        var alias = wmiClass;
+        var whereIdx = wmiClass.IndexOf(" where ", StringComparison.OrdinalIgnoreCase);
+        if (whereIdx > 0) alias = wmiClass[..whereIdx];
+
+        if (_wmiAliases.TryGetValue(alias, out var fullClass))
+        {
+            wmiClass = whereIdx > 0 ? $"{fullClass}{wmiClass[whereIdx..]}" : fullClass;
+        }
+
+        // 逐属性查询
+        var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
+        foreach (var key in keys)
+        {
+            var value = GetInfo(wmiClass, key, nameSpace);
+            if (!value.IsNullOrEmpty()) dic[key] = value;
+        }
+
+        return dic;
+    }
+
+    /// <summary>通过 WMI 查询单个属性(自动回退 wmic)。优先使用 COM 避免开进程</summary>
+    /// <param name="type">WMI 类型</param>
+    /// <param name="property">属性名</param>
+    /// <returns>属性值,失败返回 null</returns>
+    private static String? ReadWmiComSingle(String type, String property)
+    {
+        var dic = ReadWmiComMulti(type, property);
+        return dic.TryGetValue(property, out var v) && !v.IsNullOrEmpty() ? v : null;
+    }
+    #endregion
+
+#if !NETFRAMEWORK
+    #region 现代方法(非 NETFRAMEWORK)
+#if NET5_0_OR_GREATER
+    [SupportedOSPlatform("windows")]
+#endif
+    private void LoadWindowsInfo()
+    {
+        var str = "";
+
+        // 从注册表读取 MachineGuid(NET6+ 用 Registry,低版本用 reg.exe)
+#if NET6_0_OR_GREATER
+        LoadHardwareFromRegistry();
+#else
+        str = "reg".Execute(@"query HKLM\SOFTWARE\Microsoft\Cryptography /v MachineGuid", 0, false);
+        if (!str.IsNullOrEmpty() && str.Contains("REG_SZ")) Guid = str.Substring("REG_SZ", null).Trim();
+
+        var csproduct = ReadWmiComMulti("csproduct", "Name", "UUID", "Vendor");
+        if (csproduct != null)
+        {
+            if (csproduct.TryGetValue("Name", out str)) Product = str;
+            if (csproduct.TryGetValue("UUID", out str)) UUID = str;
+            if (csproduct.TryGetValue("Vendor", out str)) Vendor = str;
+        }
+#endif
+
+        // 获取操作系统名称和版本
+        var os = ReadWmiComMulti("os", "Caption", "Version");
+        if (os == null || os.Count == 0)
+        {
+            os = ReadPowerShell("Get-WmiObject Win32_OperatingSystem | Select-Object Caption, Version | ConvertTo-Json");
+        }
+        if (os is { Count: > 0 })
+        {
+            if (os.TryGetValue("Caption", out str)) OSName = str.TrimPrefix("Microsoft").Trim();
+            if (os.TryGetValue("Version", out str)) OSVersion = str;
+        }
+
+        // 磁盘和BIOS序列号
+        var diskStr = ReadWmiComSingle("diskdrive where mediatype=\"Fixed hard disk media\"", "serialnumber");
+        if (!diskStr.IsNullOrEmpty()) DiskID = diskStr?.Trim();
+
+        var sn = ReadWmiComSingle("bios", "serialnumber");
+        if (!sn.IsNullOrEmpty() && !sn.EqualIgnoreCase("System Serial Number")) Serial = sn?.Trim();
+
+        var boardStr = ReadWmiComSingle("baseboard", "serialnumber");
+        if (!boardStr.IsNullOrEmpty()) Board = boardStr?.Trim();
+
+        //// 不要在刷新里面取CPU负载,因为运行wmic会导致CPU负载很不准确,影响测量
+        //var cpu = ReadWmic("cpu", "Name", "ProcessorId", "LoadPercentage");
+        //if (cpu != null)
+        //{
+        //    if (cpu.TryGetValue("Name", out str)) Processor = str;
+        //    //if (cpu.TryGetValue("ProcessorId", out str)) CpuID = str;
+        //    if (cpu.TryGetValue("LoadPercentage", out str)) CpuRate = (Single)(str.ToDouble() / 100);
+        //}
+
+        // OS 名称和版本的兜底
+        if (OSName.IsNullOrEmpty())
+            OSName = RuntimeInformation.OSDescription.TrimPrefix("Microsoft").Trim();
+        if (OSVersion.IsNullOrEmpty())
+            OSVersion = Environment.OSVersion.Version.ToString();
+    }
+
+    /// <summary>刷新 Windows 动态数据(现代路径)</summary>
+    private void RefreshWindows()
+    {
+        RefreshMemoryAndCpu();
+
+        var power = new PowerStatus();
+
+        if (!_excludes.Contains(nameof(Temperature)))
+        {
+            var str = ReadWmiComSingle(@"/namespace:\\root\wmi path MSAcpi_ThermalZoneTemperature", "CurrentTemperature");
+            if (!str.IsNullOrEmpty())
+            {
+                Temperature = (str.SplitAsInt().Average() - 2732) / 10.0;
+            }
+            else
+            {
+                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Temperature信息无法读取");
+                _excludes.Add(nameof(Temperature));
+                Temperature = 0;
+            }
+        }
+
+        if (power.BatteryLifePercent > 0)
+            Battery = power.BatteryLifePercent;
+        else if (!_excludes.Contains(nameof(Battery)))
+        {
+            var str = ReadWmiComSingle("path win32_battery", "EstimatedChargeRemaining");
+            if (!str.IsNullOrEmpty())
+            {
+                Battery = str.SplitAsInt().Average() / 100.0;
+            }
+            else
+            {
+                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Battery信息无法读取");
+                _excludes.Add(nameof(Battery));
+                Battery = 0;
+            }
+        }
+    }
+
+    /// <summary>获取WMI信息。net*-windows反射System.Management并回退COM;其它运行时用COM并回退wmic</summary>
+    /// <param name="path">WMI路径</param>
+    /// <param name="property">属性名</param>
+    /// <param name="nameSpace">命名空间,默认 root\cimv2</param>
+    /// <returns>查询结果</returns>
+    public static String GetInfo(String path, String property, String? nameSpace = null)
+    {
+        // 规范化命名空间:WMI 要求反斜杠分隔,调用者可能传入 root/wmi 等正斜杠格式
+        if (nameSpace != null) nameSpace = nameSpace.Replace("/", "\\");
+
+        var ns = nameSpace ?? "root\\cimv2";
+
+#if __WIN__
+        // net*-windows:优先反射 System.Management 走标准 WMI,失败回退 COM;两者均失败降级 wmic 兜底
+        {
+            var value = QueryWmiSystemManagement(path, property, ns);
+            if (value != null) return value;
+
+            value = QueryWmiCom(path, property, ns);
+            if (value != null) return value;
+
+            // 两种 WMI 路径均失败(如 WMI 服务异常),降级到 wmic 进程兜底
+            if (XTrace.Log.Level <= LogLevel.Debug)
+                XTrace.WriteLine("WMI 均失败,回退 wmic: {0}.{1}", path, property);
+        }
+#else
+        // 其它运行时(netstandard/net*非windows):优先 COM,失败回退 wmic
+#if NET5_0_OR_GREATER
+        if (OperatingSystem.IsWindows())
+#else
+        if (Runtime.Windows)
+#endif
+        {
+            var value = QueryWmiCom(path, property, ns);
+            if (value != null) return value;
+        }
+#endif
+
+        // wmic 兜底(__WIN__ 降级 + 其它运行时)。不带命名空间时也要用 path 前缀以支持完整类名
+        var type = !ns.EqualIgnoreCase("root\\cimv2") ? $"/namespace:\\\\{ns} path {path}" : $"path {path}";
+        var dic = ReadWmic(type, property);
+        return dic.TryGetValue(property, out var v) ? v : "";
+    }
+
+    /// <summary>通过反射 System.Management 查询 WMI 属性。用于 net*-windows,不新增编译期依赖,不开进程</summary>
+    /// <remarks>
+    /// System.Management.dll 随 Windows 桌面运行时提供(net*-windows),通过反射调用避免核心库直接引用该程序集。
+    /// 内部使用 ManagementObjectSearcher(nameSpace, wql).Get() 枚举结果并读取属性。
+    /// </remarks>
+    /// <param name="wmiClass">WMI 类名,如 Win32_OperatingSystem</param>
+    /// <param name="property">属性名</param>
+    /// <param name="nameSpace">命名空间,如 root\cimv2</param>
+    /// <param name="throwOnError">失败时是否抛出异常,默认false吞掉异常返回null</param>
+    /// <returns>查询结果,失败返回null</returns>
+#if NET5_0_OR_GREATER
+    [SupportedOSPlatform("windows")]
+#endif
+    internal static String? QueryWmiSystemManagement(String wmiClass, String property, String nameSpace, Boolean throwOnError = false)
+    {
+        try
+        {
+            // 反射加载 System.Management(Windows桌面运行时内置,无编译期引用)
+            var asm = Assembly.Load("System.Management");
+            var searcherType = asm.GetType("System.Management.ManagementObjectSearcher")!;
+            var moType = asm.GetType("System.Management.ManagementObject")!;
+            var enumType = asm.GetType("System.Management.ManagementObjectCollection")!;
+
+            // new ManagementObjectSearcher(nameSpace, wql)
+            var wql = $"SELECT {property} FROM {wmiClass}";
+            var searcher = Activator.CreateInstance(searcherType, nameSpace, wql);
+            if (searcher == null) return null;
+
+            // .Get() → ManagementObjectCollection
+            var getMethod = searcherType.GetMethod("Get", Type.EmptyTypes)!;
+            var results = getMethod.Invoke(searcher, null);
+            if (results == null) return null;
+
+            // foreach (ManagementObject obj in results)
+            var bbs = new List<String>();
+            var getEnumeratorMethod = enumType.GetMethod("GetEnumerator")!;
+            var enumerator = getEnumeratorMethod.Invoke(results, null);
+            var moveNextMethod = enumerator!.GetType().GetMethod("MoveNext")!;
+            var currentProperty = enumerator.GetType().GetProperty("Current")!;
+
+            var itemProp = moType.GetProperty("Item", [typeof(String)])!;
+            var getValueMethod = itemProp.GetGetMethod()!;
+
+            while ((Boolean)moveNextMethod.Invoke(enumerator, null)!)
+            {
+                var obj = currentProperty.GetValue(enumerator);
+                if (obj == null) continue;
+
+                var val = getValueMethod.Invoke(obj, [property]);
+                if (val != null)
+                {
+                    var v = val.ToString()?.TrimInvisible()?.Trim();
+                    if (!v.IsNullOrEmpty()) bbs.Add(v);
+                }
+            }
+
+            // 清理 COM 引用
+            if (results is IDisposable d1) d1.Dispose();
+            if (searcher is IDisposable d2) d2.Dispose();
+
+            if (bbs.Count > 0)
+            {
+                bbs.Sort();
+                return bbs.Distinct().Join();
+            }
+        }
+        catch (Exception ex)
+        {
+            if (throwOnError) throw;
+            if (XTrace.Log.Level <= LogLevel.Debug)
+            {
+                XTrace.WriteLine("QueryWmiSystemManagement 失败: {0}.{1} @ {2}", wmiClass, property, nameSpace);
+                XTrace.WriteException(ex is TargetInvocationException tie ? tie.InnerException! : ex);
+            }
+        }
+
+        return null;
+    }
+
+    /// <summary>通过 COM 查询 WMI 属性。使用 Windows 内置 winmgmts 组件,不新增依赖,不开进程</summary>
+    /// <remarks>
+    /// 通过 winmgmts moniker 绑定 SWbemServices,执行 WQL 查询并枚举结果,SWbemObject 按名暴露 WMI 属性。
+    /// 相比 System.Management 无需该程序集,适用于 netstandard/net*非windows 运行时下的 Windows 主机。
+    /// COM 查询在部分环境可能失败,此时调用方应回退 wmic。
+    /// </remarks>
+    /// <param name="wmiClass">WMI 类名,如 Win32_OperatingSystem</param>
+    /// <param name="property">属性名</param>
+    /// <param name="nameSpace">命名空间,如 root\cimv2</param>
+    /// <param name="throwOnError">失败时是否抛出异常,默认false吞掉异常返回null</param>
+    /// <returns>查询结果,失败返回null</returns>
+#if NET5_0_OR_GREATER
+    [SupportedOSPlatform("windows")]
+#endif
+    internal static String? QueryWmiCom(String wmiClass, String property, String nameSpace, Boolean throwOnError = false)
+    {
+        Object? services = null;
+        Object? results = null;
+        try
+        {
+            // 通过 winmgmts moniker 绑定 WMI 服务,规避 SWbemLocator.ConnectServer 的 IDispatch 参数编组问题
+            var moniker = $"winmgmts:\\\\.\\{nameSpace}";
+            services = Marshal.BindToMoniker(moniker);
+            if (services == null) return null;
+
+            // ExecQuery 执行 WQL 查询,返回 SWbemObjectSet
+            var wql = $"SELECT {property} FROM {wmiClass}";
+            results = services.GetType().InvokeMember("ExecQuery", BindingFlags.InvokeMethod, null, services, new Object[] { wql });
+            if (results == null) return null;
+
+            // SWbemObjectSet 实现 IEnumVARIANT,可直接 foreach;SWbemObject 通过 IDispatch 按名暴露 WMI 属性
+            var bbs = new List<String>();
+            foreach (var obj in (System.Collections.IEnumerable)results)
+            {
+                if (obj == null) continue;
+                try
+                {
+                    var val = obj.GetType().InvokeMember(property, BindingFlags.GetProperty, null, obj, null);
+                    if (val != null)
+                    {
+                        var v = val.ToString()?.TrimInvisible()?.Trim();
+                        if (!v.IsNullOrEmpty()) bbs.Add(v);
+                    }
+                }
+                finally { try { Marshal.FinalReleaseComObject(obj); } catch { } }
+            }
+
+            if (bbs.Count > 0)
+            {
+                bbs.Sort();
+                return bbs.Distinct().Join();
+            }
+        }
+        catch (Exception ex)
+        {
+            if (throwOnError) throw;
+            if (XTrace.Log.Level <= LogLevel.Debug)
+            {
+                XTrace.WriteLine("QueryWmiCom 失败: {0}.{1} @ {2}", wmiClass, property, nameSpace);
+                XTrace.WriteException(ex is TargetInvocationException tie ? tie.InnerException! : ex);
+            }
+        }
+        finally
+        {
+            if (results != null) try { Marshal.FinalReleaseComObject(results); } catch { }
+            if (services != null) try { Marshal.FinalReleaseComObject(services); } catch { }
+        }
+
+        return null;
+    }
+    #endregion
+#endif
+}
Added +150 -0
diff --git a/NewLife.Core/Common/MachineInfo.Windows.NetFX.cs b/NewLife.Core/Common/MachineInfo.Windows.NetFX.cs
new file mode 100644
index 0000000..2eac212
--- /dev/null
+++ b/NewLife.Core/Common/MachineInfo.Windows.NetFX.cs
@@ -0,0 +1,150 @@
+#if NETFRAMEWORK
+using System.Management;
+using Microsoft.VisualBasic.Devices;
+using Microsoft.Win32;
+using NewLife.Log;
+using NewLife.Reflection;
+using NewLife.Windows;
+
+namespace NewLife;
+
+/// <summary>机器信息 Windows NETFRAMEWORK 兼容部分。覆盖 Windows.cs 中的同名方法</summary>
+public partial class MachineInfo
+{
+    /// <summary>加载 Windows 静态信息(NETFRAMEWORK 路径)</summary>
+    private void LoadWindowsInfo()
+    {
+        // 从注册表读取硬件信息
+        LoadHardwareFromRegistry();
+
+        // 获取内存大小
+        {
+            var ci = new ComputerInfo();
+            Memory = ci.TotalPhysicalMemory;
+        }
+
+        // 获取操作系统名称和版本
+        try
+        {
+            var ci = new ComputerInfo();
+
+            // 系统名取WMI可能出错
+            OSName = ci.OSFullName?.Replace("®", null).TrimPrefix("Microsoft").Trim();
+            OSVersion = ci.OSVersion;
+        }
+        catch
+        {
+            try
+            {
+                var reg2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
+                if (reg2 != null)
+                {
+                    OSName = reg2.GetValue("ProductName") + "";
+                    OSVersion = reg2.GetValue("ReleaseId") + "";
+                }
+            }
+            catch (Exception ex)
+            {
+                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteException(ex);
+            }
+        }
+
+        // 磁盘和BIOS序列号
+        //Processor = GetInfo("Win32_Processor", "Name");
+        //CpuID = GetInfo("Win32_Processor", "ProcessorId");
+        //var uuid = GetInfo("Win32_ComputerSystemProduct", "UUID");
+        //Product = GetInfo("Win32_ComputerSystemProduct", "Name");
+        DiskID = GetInfo("Win32_DiskDrive where mediatype=\"Fixed hard disk media\"", "SerialNumber");
+
+        var sn = GetInfo("Win32_BIOS", "SerialNumber");
+        if (!sn.IsNullOrEmpty() && !sn.EqualIgnoreCase("System Serial Number")) Serial = sn;
+        Board = GetInfo("Win32_BaseBoard", "SerialNumber");
+    }
+
+    /// <summary>刷新 Windows 动态数据(NETFRAMEWORK 路径)</summary>
+    private void RefreshWindows()
+    {
+        RefreshMemoryAndCpu();
+
+        var power = new PowerStatus();
+
+        if (!_excludes.Contains(nameof(Temperature)))
+        {
+            // 读取主板温度,不太准。标准方案是ring0通过IOPort读取CPU温度,太难在基础类库实现
+            var str = GetInfo("Win32_TemperatureProbe", "CurrentReading");
+            if (!str.IsNullOrEmpty())
+            {
+                Temperature = str.SplitAsInt().Average();
+            }
+            else
+            {
+                str = GetInfo("MSAcpi_ThermalZoneTemperature", "CurrentTemperature", "root/wmi");
+                if (!str.IsNullOrEmpty())
+                    Temperature = (str.SplitAsInt().Average() - 2732) / 10.0;
+                else
+                {
+                    if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Temperature信息无法读取");
+                    _excludes.Add(nameof(Temperature));
+                    Temperature = 0;
+                }
+            }
+        }
+
+        if (power.BatteryLifePercent > 0)
+            Battery = power.BatteryLifePercent;
+        else if (!_excludes.Contains(nameof(Battery)))
+        {
+            // 电池剩余
+            var str = GetInfo("Win32_Battery", "EstimatedChargeRemaining");
+            if (!str.IsNullOrEmpty())
+                Battery = str.SplitAsInt().Average() / 100.0;
+            else
+            {
+                if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("Battery信息无法读取");
+                _excludes.Add(nameof(Battery));
+                Battery = 0;
+            }
+        }
+    }
+
+    /// <summary>获取WMI信息(NETFRAMEWORK 路径,直接使用 ManagementObjectSearcher)</summary>
+    /// <param name="path">WMI路径</param>
+    /// <param name="property">属性名</param>
+    /// <param name="nameSpace">命名空间,默认 root\cimv2</param>
+    /// <returns>查询结果</returns>
+    public static String GetInfo(String path, String property, String? nameSpace = null)
+    {
+        // 规范化命名空间:WMI 要求反斜杠分隔,调用者可能传入 root/wmi 等正斜杠格式
+        if (nameSpace != null) nameSpace = nameSpace.Replace("/", "\\");
+
+        // Linux Mono不支持WMI
+        if (Runtime.Mono) return "";
+
+        var bbs = new List<String>();
+        try
+        {
+            var wql = $"Select {property} From {path}";
+            var cimobject = new ManagementObjectSearcher(nameSpace, wql);
+            var moc = cimobject.Get();
+            foreach (var mo in moc)
+            {
+                var val = mo?.Properties?[property]?.Value;
+                if (val != null)
+                {
+                    var v = val.ToString().TrimInvisible()?.Trim();
+                    if (v != null) bbs.Add(v);
+                }
+            }
+        }
+        catch (Exception ex)
+        {
+            if (XTrace.Log.Level <= LogLevel.Debug) XTrace.WriteLine("WMI.GetInfo({0})失败!{1}", path, ex.Message);
+            return "";
+        }
+
+        bbs.Sort();
+
+        return bbs.Distinct().Join();
+    }
+}
+#endif