281 lines
10 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|