281 lines
10 KiB
C#

using System.Net;
using System.Text;
using System.Xml.Linq;
namespace AmtScanner.Api.Services;
public interface IAmtPowerService
{
Task<AmtPowerState?> GetPowerStateAsync(string ip, string username, string password);
Task<bool> PowerOnAsync(string ip, string username, string password);
Task<bool> PowerOffAsync(string ip, string username, string password);
Task<bool> ResetAsync(string ip, string username, string password);
Task<bool> PowerCycleAsync(string ip, string username, string password);
Task<bool> HardPowerOffAsync(string ip, string username, string password);
Task<bool> HardResetAsync(string ip, string username, string password);
}
public class AmtPowerState
{
public int PowerState { get; set; }
public string PowerStateText { get; set; } = string.Empty;
}
public class AmtPowerService : IAmtPowerService
{
private readonly ILogger<AmtPowerService> _logger;
private const int AMT_PORT = 16992;
// 电源状态枚举
private static readonly Dictionary<int, string> PowerStateMap = new()
{
{ 1, "其他" },
{ 2, "开机" },
{ 3, "轻度睡眠" },
{ 4, "深度睡眠" },
{ 5, "软关机循环" },
{ 6, "硬关机" },
{ 7, "休眠" },
{ 8, "软关机" },
{ 9, "硬关机循环" },
{ 10, "主总线复位" },
{ 11, "诊断中断" },
{ 12, "优雅关机" },
{ 14, "优雅重启" },
{ 15, "从省电模式唤醒" }
};
public AmtPowerService(ILogger<AmtPowerService> logger)
{
_logger = logger;
}
public async Task<AmtPowerState?> GetPowerStateAsync(string ip, string username, string password)
{
try
{
// 获取 CIM_AssociatedPowerManagementService
var soapEnvelope = BuildGetPowerStateRequest(ip, AMT_PORT);
var response = await SendWsmanRequestAsync(ip, AMT_PORT, username, password, soapEnvelope);
if (response == null)
{
_logger.LogWarning("无法获取电源状态");
return null;
}
return ParsePowerState(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取电源状态失败: {Ip}", ip);
return null;
}
}
public async Task<bool> PowerOnAsync(string ip, string username, string password)
{
return await ChangePowerStateAsync(ip, username, password, 2, "开机");
}
public async Task<bool> PowerOffAsync(string ip, string username, string password)
{
// 使用 12 (GracefulOff) 优雅关机,让操作系统正常关闭
// 8 (SoftOff) 是硬关机,会直接断电导致 AMT 掉线
return await ChangePowerStateAsync(ip, username, password, 12, "关机");
}
public async Task<bool> ResetAsync(string ip, string username, string password)
{
// 使用 14 (GracefulReset) 优雅重启
// 10 (MasterBusReset) 是硬重启
return await ChangePowerStateAsync(ip, username, password, 14, "重启");
}
public async Task<bool> PowerCycleAsync(string ip, string username, string password)
{
// 5 (SoftPowerCycle) 电源循环 - 这个会断电再上电
return await ChangePowerStateAsync(ip, username, password, 5, "电源循环");
}
public async Task<bool> HardPowerOffAsync(string ip, string username, string password)
{
// 8 (SoftOff) 硬关机 - 直接断电
return await ChangePowerStateAsync(ip, username, password, 8, "强制关机");
}
public async Task<bool> HardResetAsync(string ip, string username, string password)
{
// 10 (MasterBusReset) 硬重启
return await ChangePowerStateAsync(ip, username, password, 10, "强制重启");
}
private async Task<bool> ChangePowerStateAsync(string ip, string username, string password, int powerState, string actionName)
{
try
{
var soapEnvelope = BuildChangePowerStateRequest(ip, AMT_PORT, powerState);
var response = await SendWsmanRequestAsync(ip, AMT_PORT, username, password, soapEnvelope);
if (response == null)
{
_logger.LogWarning("{Action}请求无响应", actionName);
return false;
}
// 检查返回值
var doc = XDocument.Parse(response);
var returnValue = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "ReturnValue")?.Value;
if (returnValue == "0")
{
_logger.LogInformation("{Action}成功: {Ip}", actionName, ip);
return true;
}
_logger.LogWarning("{Action}失败,返回值: {ReturnValue}", actionName, returnValue);
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "{Action}失败: {Ip}", actionName, ip);
return false;
}
}
private async Task<string?> SendWsmanRequestAsync(string ip, int port, string username, string password, string soapEnvelope)
{
var url = $"http://{ip}:{port}/wsman";
var handler = new HttpClientHandler
{
Credentials = new CredentialCache
{
{ new Uri($"http://{ip}:{port}"), "Digest", new NetworkCredential(username, password) }
},
PreAuthenticate = false
};
using var client = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(5)
};
var content = new StringContent(soapEnvelope, Encoding.UTF8, "application/soap+xml");
try
{
var response = await client.PostAsync(url, content);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
return responseContent;
}
_logger.LogWarning("WS-Management 请求失败: {StatusCode}", response.StatusCode);
return null;
}
catch (Exception ex)
{
_logger.LogWarning("HTTP 请求异常: {Message}", ex.Message);
return null;
}
}
private static string BuildGetPowerStateRequest(string ip, int port)
{
var toAddress = $"http://{ip}:{port}/wsman";
var messageId = $"uuid:{Guid.NewGuid()}";
// 获取 CIM_AssociatedPowerManagementService
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope""
xmlns:a=""http://schemas.xmlsoap.org/ws/2004/08/addressing""
xmlns:w=""http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"">
<s:Header>
<a:To>{toAddress}</a:To>
<w:ResourceURI s:mustUnderstand=""true"">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_AssociatedPowerManagementService</w:ResourceURI>
<a:ReplyTo>
<a:Address s:mustUnderstand=""true"">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
</a:ReplyTo>
<a:Action s:mustUnderstand=""true"">http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</a:Action>
<w:MaxEnvelopeSize s:mustUnderstand=""true"">153600</w:MaxEnvelopeSize>
<a:MessageID>{messageId}</a:MessageID>
<w:Locale xml:lang=""en-US"" s:mustUnderstand=""false"" />
<w:OperationTimeout>PT60.000S</w:OperationTimeout>
</s:Header>
<s:Body/>
</s:Envelope>";
}
private static string BuildChangePowerStateRequest(string ip, int port, int powerState)
{
var toAddress = $"http://{ip}:{port}/wsman";
var messageId = $"uuid:{Guid.NewGuid()}";
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope""
xmlns:a=""http://schemas.xmlsoap.org/ws/2004/08/addressing""
xmlns:w=""http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"">
<s:Header>
<a:To>{toAddress}</a:To>
<w:ResourceURI s:mustUnderstand=""true"">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService</w:ResourceURI>
<a:ReplyTo>
<a:Address s:mustUnderstand=""true"">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
</a:ReplyTo>
<a:Action s:mustUnderstand=""true"">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService/RequestPowerStateChange</a:Action>
<w:MaxEnvelopeSize s:mustUnderstand=""true"">153600</w:MaxEnvelopeSize>
<a:MessageID>{messageId}</a:MessageID>
<w:Locale xml:lang=""en-US"" s:mustUnderstand=""false"" />
<w:OperationTimeout>PT60.000S</w:OperationTimeout>
<w:SelectorSet>
<w:Selector Name=""Name"">Intel(r) AMT Power Management Service</w:Selector>
</w:SelectorSet>
</s:Header>
<s:Body>
<p:RequestPowerStateChange_INPUT xmlns:p=""http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_PowerManagementService"">
<p:PowerState>{powerState}</p:PowerState>
<p:ManagedElement>
<a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
<a:ReferenceParameters>
<w:ResourceURI>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ComputerSystem</w:ResourceURI>
<w:SelectorSet>
<w:Selector Name=""Name"">ManagedSystem</w:Selector>
</w:SelectorSet>
</a:ReferenceParameters>
</p:ManagedElement>
</p:RequestPowerStateChange_INPUT>
</s:Body>
</s:Envelope>";
}
private AmtPowerState? ParsePowerState(string xmlResponse)
{
try
{
var doc = XDocument.Parse(xmlResponse);
var powerStateElement = doc.Descendants().FirstOrDefault(e => e.Name.LocalName == "PowerState");
if (powerStateElement == null)
{
return null;
}
var powerState = int.Parse(powerStateElement.Value);
var powerStateText = PowerStateMap.TryGetValue(powerState, out var text) ? text : "未知";
return new AmtPowerState
{
PowerState = powerState,
PowerStateText = powerStateText
};
}
catch (Exception ex)
{
_logger.LogError(ex, "解析电源状态失败");
return null;
}
}
}