681 lines
26 KiB
C#
681 lines
26 KiB
C#
using AmtScanner.Api.Models;
|
|
using Intel.Management.Wsman;
|
|
using System.Security;
|
|
|
|
namespace AmtScanner.Api.Services;
|
|
|
|
public class AmtHardwareQueryService : IAmtHardwareQueryService
|
|
{
|
|
private readonly ILogger<AmtHardwareQueryService> _logger;
|
|
|
|
public AmtHardwareQueryService(ILogger<AmtHardwareQueryService> logger)
|
|
{
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<HardwareInfo> QueryHardwareInfoAsync(
|
|
string ipAddress,
|
|
string username,
|
|
string password,
|
|
List<int> openPorts)
|
|
{
|
|
var hardwareInfo = new HardwareInfo
|
|
{
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
LastUpdated = DateTime.UtcNow
|
|
};
|
|
|
|
try
|
|
{
|
|
// Determine protocol and port
|
|
string protocol;
|
|
int port;
|
|
|
|
if (openPorts.Contains(16992))
|
|
{
|
|
protocol = "http";
|
|
port = 16992;
|
|
}
|
|
else if (openPorts.Contains(16993))
|
|
{
|
|
protocol = "https";
|
|
port = 16993;
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("No suitable AMT port found");
|
|
}
|
|
|
|
_logger.LogInformation("Querying hardware info from {Protocol}://{Ip}:{Port}", protocol, ipAddress, port);
|
|
|
|
// Create secure password
|
|
var securePassword = new SecureString();
|
|
foreach (char c in password)
|
|
{
|
|
securePassword.AppendChar(c);
|
|
}
|
|
securePassword.MakeReadOnly();
|
|
|
|
// Create WS-Management connection
|
|
var connection = new WsmanConnection();
|
|
connection.Address = $"{protocol}://{ipAddress}:{port}/wsman";
|
|
connection.SetCredentials(username, securePassword);
|
|
|
|
// Accept self-signed certificates for HTTPS
|
|
if (protocol == "https")
|
|
{
|
|
connection.Options.ServerCertificateValidationCallback = (certificate, sslPolicyErrors) =>
|
|
{
|
|
if (certificate.Subject.Equals(certificate.Issuer))
|
|
{
|
|
return true;
|
|
}
|
|
if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
await Task.Run(() =>
|
|
{
|
|
// Query all hardware components sequentially (not in parallel)
|
|
QuerySystemInfo(connection, hardwareInfo);
|
|
QueryProcessorInfo(connection, hardwareInfo);
|
|
QueryMemoryInfo(connection, hardwareInfo);
|
|
QueryStorageInfo(connection, hardwareInfo);
|
|
});
|
|
|
|
_logger.LogInformation("Successfully queried hardware info from {Ip}", ipAddress);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error querying hardware info from {Ip}", ipAddress);
|
|
throw;
|
|
}
|
|
|
|
return hardwareInfo;
|
|
}
|
|
|
|
private void QuerySystemInfo(IWsmanConnection connection, HardwareInfo hardwareInfo)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Querying system information");
|
|
|
|
var query = connection.ExecQuery("SELECT * FROM CIM_ComputerSystem WHERE Name='ManagedSystem'");
|
|
|
|
int count = 0;
|
|
foreach (IWsmanItem item in query)
|
|
{
|
|
count++;
|
|
_logger.LogDebug("Found CIM_ComputerSystem item {Count}", count);
|
|
|
|
var manufacturer = item.Object.GetProperty("Manufacturer");
|
|
_logger.LogDebug("Manufacturer IsNull: {IsNull}, Value: {Value}", manufacturer.IsNull, manufacturer.IsNull ? "null" : manufacturer.ToString());
|
|
if (!manufacturer.IsNull)
|
|
{
|
|
hardwareInfo.SystemManufacturer = manufacturer.ToString();
|
|
}
|
|
|
|
var model = item.Object.GetProperty("Model");
|
|
_logger.LogDebug("Model IsNull: {IsNull}, Value: {Value}", model.IsNull, model.IsNull ? "null" : model.ToString());
|
|
if (!model.IsNull)
|
|
{
|
|
hardwareInfo.SystemModel = model.ToString();
|
|
}
|
|
|
|
// Try to get serial number (may not be available)
|
|
try
|
|
{
|
|
var serialNumber = item.Object.GetProperty("SerialNumber");
|
|
_logger.LogDebug("SerialNumber IsNull: {IsNull}, Value: {Value}", serialNumber.IsNull, serialNumber.IsNull ? "null" : serialNumber.ToString());
|
|
if (!serialNumber.IsNull)
|
|
{
|
|
hardwareInfo.SystemSerialNumber = serialNumber.ToString();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "Serial number property not available");
|
|
}
|
|
|
|
break; // Only need first result
|
|
}
|
|
|
|
if (count == 0)
|
|
{
|
|
_logger.LogWarning("No CIM_ComputerSystem items found");
|
|
}
|
|
|
|
_logger.LogDebug("System info: {Manufacturer} {Model}",
|
|
hardwareInfo.SystemManufacturer, hardwareInfo.SystemModel);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to query system information");
|
|
}
|
|
}
|
|
|
|
private void QueryProcessorInfo(IWsmanConnection connection, HardwareInfo hardwareInfo)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Querying processor information");
|
|
|
|
var query = connection.ExecQuery("SELECT * FROM CIM_Realizes");
|
|
foreach (IWsmanItem item in query)
|
|
{
|
|
if (item.Object.GetProperty("Dependent").IsA("CIM_Processor"))
|
|
{
|
|
var cpuObj = item.Object.GetProperty("Dependent").Ref.Get();
|
|
var chipObj = item.Object.GetProperty("Antecedent").Ref.Get();
|
|
|
|
// Try to get CPU model from Chip Version (more detailed)
|
|
try
|
|
{
|
|
var version = chipObj.GetProperty("Version");
|
|
if (!version.IsNull && !string.IsNullOrWhiteSpace(version.ToString()))
|
|
{
|
|
hardwareInfo.ProcessorModel = version.ToString().Trim();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Version not available, fall back to Family
|
|
}
|
|
|
|
// If no version, use processor family
|
|
if (string.IsNullOrEmpty(hardwareInfo.ProcessorModel))
|
|
{
|
|
var family = cpuObj.GetProperty("Family");
|
|
if (!family.IsNull)
|
|
{
|
|
hardwareInfo.ProcessorModel = MapProcessorFamily(family.ToString());
|
|
}
|
|
}
|
|
|
|
// Get clock speeds
|
|
var maxClockSpeed = cpuObj.GetProperty("MaxClockSpeed");
|
|
if (!maxClockSpeed.IsNull)
|
|
{
|
|
hardwareInfo.ProcessorMaxClockSpeed = int.Parse(maxClockSpeed.ToString());
|
|
}
|
|
|
|
var currentClockSpeed = cpuObj.GetProperty("CurrentClockSpeed");
|
|
if (!currentClockSpeed.IsNull)
|
|
{
|
|
hardwareInfo.ProcessorCurrentClockSpeed = int.Parse(currentClockSpeed.ToString());
|
|
}
|
|
|
|
// Try to get core count (may not be available on all systems)
|
|
try
|
|
{
|
|
var numberOfCores = cpuObj.GetProperty("NumberOfCores");
|
|
if (!numberOfCores.IsNull)
|
|
{
|
|
hardwareInfo.ProcessorCores = int.Parse(numberOfCores.ToString());
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Core count not available
|
|
}
|
|
|
|
// Try to get thread count
|
|
try
|
|
{
|
|
var numberOfLogicalProcessors = cpuObj.GetProperty("NumberOfLogicalProcessors");
|
|
if (!numberOfLogicalProcessors.IsNull)
|
|
{
|
|
hardwareInfo.ProcessorThreads = int.Parse(numberOfLogicalProcessors.ToString());
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Thread count not available
|
|
}
|
|
|
|
break; // Only need first processor
|
|
}
|
|
}
|
|
|
|
_logger.LogDebug("Processor info: {Model} @ {Speed}MHz",
|
|
hardwareInfo.ProcessorModel, hardwareInfo.ProcessorMaxClockSpeed);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to query processor information");
|
|
}
|
|
}
|
|
|
|
private void QueryMemoryInfo(IWsmanConnection connection, HardwareInfo hardwareInfo)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Querying memory information");
|
|
|
|
var query = connection.ExecQuery("SELECT * FROM CIM_PhysicalMemory");
|
|
long totalMemory = 0;
|
|
|
|
foreach (IWsmanItem item in query)
|
|
{
|
|
var module = new MemoryModule();
|
|
|
|
var tag = item.Object.GetProperty("Tag");
|
|
if (!tag.IsNull)
|
|
{
|
|
module.SlotLocation = tag.ToString();
|
|
}
|
|
|
|
var bankLabel = item.Object.GetProperty("BankLabel");
|
|
if (!bankLabel.IsNull && string.IsNullOrEmpty(module.SlotLocation))
|
|
{
|
|
module.SlotLocation = bankLabel.ToString();
|
|
}
|
|
|
|
var capacity = item.Object.GetProperty("Capacity");
|
|
if (!capacity.IsNull)
|
|
{
|
|
module.CapacityBytes = long.Parse(capacity.ToString());
|
|
totalMemory += module.CapacityBytes.Value;
|
|
}
|
|
|
|
// Try ConfiguredMemoryClockSpeed first (this is in MHz, available in newer AMT versions)
|
|
try
|
|
{
|
|
var configuredSpeed = item.Object.GetProperty("ConfiguredMemoryClockSpeed");
|
|
_logger.LogDebug("Memory ConfiguredMemoryClockSpeed: IsNull={IsNull}, Value={Value}",
|
|
configuredSpeed.IsNull, configuredSpeed.IsNull ? "null" : configuredSpeed.ToString());
|
|
if (!configuredSpeed.IsNull)
|
|
{
|
|
var configuredSpeedValue = int.Parse(configuredSpeed.ToString());
|
|
if (configuredSpeedValue > 0)
|
|
{
|
|
module.SpeedMHz = configuredSpeedValue;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "ConfiguredMemoryClockSpeed not available");
|
|
}
|
|
|
|
// If ConfiguredMemoryClockSpeed not available, try MaxMemorySpeed (also in MHz)
|
|
if (module.SpeedMHz == null || module.SpeedMHz == 0)
|
|
{
|
|
try
|
|
{
|
|
var maxSpeed = item.Object.GetProperty("MaxMemorySpeed");
|
|
_logger.LogDebug("Memory MaxMemorySpeed: IsNull={IsNull}, Value={Value}",
|
|
maxSpeed.IsNull, maxSpeed.IsNull ? "null" : maxSpeed.ToString());
|
|
if (!maxSpeed.IsNull)
|
|
{
|
|
var maxSpeedValue = int.Parse(maxSpeed.ToString());
|
|
if (maxSpeedValue > 0)
|
|
{
|
|
module.SpeedMHz = maxSpeedValue;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "MaxMemorySpeed not available");
|
|
}
|
|
}
|
|
|
|
// Speed property is in nanoseconds, convert to MHz if other properties not available
|
|
// Note: Speed in nanoseconds can be converted to MHz: MHz = 1000 / nanoseconds
|
|
if (module.SpeedMHz == null || module.SpeedMHz == 0)
|
|
{
|
|
var speed = item.Object.GetProperty("Speed");
|
|
_logger.LogDebug("Memory Speed (nanoseconds): IsNull={IsNull}, Value={Value}",
|
|
speed.IsNull, speed.IsNull ? "null" : speed.ToString());
|
|
if (!speed.IsNull)
|
|
{
|
|
var speedNs = int.Parse(speed.ToString());
|
|
if (speedNs > 0)
|
|
{
|
|
// Convert nanoseconds to MHz: MHz = 1000 / ns
|
|
// For example: 0.625 ns = 1600 MHz, but AMT typically returns 0 for this
|
|
// If speed is a reasonable nanosecond value (like 1-100), convert it
|
|
if (speedNs <= 100)
|
|
{
|
|
module.SpeedMHz = (int)(1000.0 / speedNs);
|
|
}
|
|
else
|
|
{
|
|
// Some older AMT versions might return MHz directly in Speed field
|
|
module.SpeedMHz = speedNs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var memoryType = item.Object.GetProperty("MemoryType");
|
|
if (!memoryType.IsNull)
|
|
{
|
|
module.MemoryType = MapMemoryType(memoryType.ToString());
|
|
}
|
|
|
|
// Try to get FormFactor
|
|
try
|
|
{
|
|
var formFactor = item.Object.GetProperty("FormFactor");
|
|
if (!formFactor.IsNull)
|
|
{
|
|
var formFactorStr = MapFormFactor(formFactor.ToString());
|
|
_logger.LogDebug("Memory FormFactor: {FormFactor}", formFactorStr);
|
|
// We could add FormFactor to MemoryModule model if needed
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "FormFactor not available");
|
|
}
|
|
|
|
var manufacturer = item.Object.GetProperty("Manufacturer");
|
|
if (!manufacturer.IsNull)
|
|
{
|
|
module.Manufacturer = manufacturer.ToString();
|
|
}
|
|
|
|
var partNumber = item.Object.GetProperty("PartNumber");
|
|
if (!partNumber.IsNull)
|
|
{
|
|
module.PartNumber = partNumber.ToString();
|
|
}
|
|
|
|
try
|
|
{
|
|
var serialNumber = item.Object.GetProperty("SerialNumber");
|
|
if (!serialNumber.IsNull)
|
|
{
|
|
module.SerialNumber = serialNumber.ToString();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Serial number not available
|
|
}
|
|
|
|
hardwareInfo.MemoryModules.Add(module);
|
|
}
|
|
|
|
hardwareInfo.TotalMemoryBytes = totalMemory;
|
|
|
|
_logger.LogDebug("Memory info: {Count} modules, {TotalGB}GB total",
|
|
hardwareInfo.MemoryModules.Count, totalMemory / 1024 / 1024 / 1024);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to query memory information");
|
|
}
|
|
}
|
|
|
|
private void QueryStorageInfo(IWsmanConnection connection, HardwareInfo hardwareInfo)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogDebug("Querying storage information");
|
|
|
|
var query = connection.ExecQuery("SELECT * FROM CIM_MediaAccessDevice");
|
|
|
|
foreach (IWsmanItem item in query)
|
|
{
|
|
var device = new StorageDevice();
|
|
|
|
var deviceId = item.Object.GetProperty("DeviceID");
|
|
if (!deviceId.IsNull)
|
|
{
|
|
device.DeviceId = deviceId.ToString();
|
|
}
|
|
|
|
// Try to get model from CIM_PhysicalPackage via CIM_Realizes association
|
|
// This is how the SDK gets the model number
|
|
try
|
|
{
|
|
var realizesQuery = connection.ExecQuery("SELECT * FROM CIM_Realizes");
|
|
foreach (IWsmanItem realizesItem in realizesQuery)
|
|
{
|
|
try
|
|
{
|
|
var dependent = realizesItem.Object.GetProperty("Dependent");
|
|
if (!dependent.IsNull && dependent.IsA("CIM_MediaAccessDevice"))
|
|
{
|
|
// Check if this is the same device
|
|
var depObj = dependent.Ref.Get();
|
|
var depDeviceId = depObj.GetProperty("DeviceID");
|
|
if (!depDeviceId.IsNull && depDeviceId.ToString() == device.DeviceId)
|
|
{
|
|
var antecedent = realizesItem.Object.GetProperty("Antecedent");
|
|
if (!antecedent.IsNull && antecedent.IsA("CIM_PhysicalPackage"))
|
|
{
|
|
var packageObj = antecedent.Ref.Get();
|
|
|
|
var model = packageObj.GetProperty("Model");
|
|
if (!model.IsNull && !string.IsNullOrWhiteSpace(model.ToString()))
|
|
{
|
|
device.Model = model.ToString().Trim();
|
|
_logger.LogDebug("Storage Model from CIM_PhysicalPackage: {Model}", device.Model);
|
|
}
|
|
|
|
// Also try to get serial number from package
|
|
try
|
|
{
|
|
var serialNumber = packageObj.GetProperty("SerialNumber");
|
|
if (!serialNumber.IsNull && !string.IsNullOrWhiteSpace(serialNumber.ToString()))
|
|
{
|
|
_logger.LogDebug("Storage SerialNumber from CIM_PhysicalPackage: {SerialNumber}", serialNumber.ToString());
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "Error processing CIM_Realizes item");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogDebug(ex, "Failed to query CIM_Realizes for storage model");
|
|
}
|
|
|
|
// Fallback: try to get model from MediaAccessDevice properties
|
|
if (string.IsNullOrEmpty(device.Model))
|
|
{
|
|
var caption = item.Object.GetProperty("Caption");
|
|
if (!caption.IsNull && !string.IsNullOrWhiteSpace(caption.ToString()))
|
|
{
|
|
device.Model = caption.ToString().Trim();
|
|
}
|
|
}
|
|
|
|
// If Caption is empty, try Name
|
|
if (string.IsNullOrEmpty(device.Model))
|
|
{
|
|
try
|
|
{
|
|
var name = item.Object.GetProperty("Name");
|
|
if (!name.IsNull && !string.IsNullOrWhiteSpace(name.ToString()))
|
|
{
|
|
device.Model = name.ToString().Trim();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// If still empty, try Description
|
|
if (string.IsNullOrEmpty(device.Model))
|
|
{
|
|
try
|
|
{
|
|
var description = item.Object.GetProperty("Description");
|
|
if (!description.IsNull && !string.IsNullOrWhiteSpace(description.ToString()))
|
|
{
|
|
device.Model = description.ToString().Trim();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// Try different capacity properties
|
|
try
|
|
{
|
|
var maxMediaSize = item.Object.GetProperty("MaxMediaSize");
|
|
if (!maxMediaSize.IsNull)
|
|
{
|
|
device.CapacityBytes = long.Parse(maxMediaSize.ToString()) * 1024; // Convert KB to bytes
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
try
|
|
{
|
|
var capacity = item.Object.GetProperty("Capacity");
|
|
if (!capacity.IsNull)
|
|
{
|
|
device.CapacityBytes = long.Parse(capacity.ToString());
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Capacity not available
|
|
}
|
|
}
|
|
|
|
// Try to get interface type
|
|
try
|
|
{
|
|
var interfaceType = item.Object.GetProperty("InterfaceType");
|
|
if (!interfaceType.IsNull)
|
|
{
|
|
device.InterfaceType = interfaceType.ToString();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Interface type not available
|
|
}
|
|
|
|
hardwareInfo.StorageDevices.Add(device);
|
|
}
|
|
|
|
_logger.LogDebug("Storage info: {Count} devices", hardwareInfo.StorageDevices.Count);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to query storage information");
|
|
}
|
|
}
|
|
|
|
private string MapProcessorFamily(string familyCode)
|
|
{
|
|
// Map processor family codes to readable names
|
|
// Based on DMTF SMBIOS Reference Specification
|
|
return familyCode switch
|
|
{
|
|
"2" => "Unknown",
|
|
"3" => "8086",
|
|
"4" => "80286",
|
|
"5" => "80386",
|
|
"6" => "80486",
|
|
"11" => "Pentium",
|
|
"12" => "Pentium Pro",
|
|
"13" => "Pentium II",
|
|
"15" => "Celeron",
|
|
"16" => "Pentium II Xeon",
|
|
"17" => "Pentium III",
|
|
"179" => "Intel Core i3",
|
|
"180" => "Intel Core i5",
|
|
"181" => "Intel Core i7",
|
|
"183" => "Intel Core i9",
|
|
"198" => "Intel Core", // Generic Intel Core processor
|
|
"205" => "Intel Core i9 (12th Gen)",
|
|
"206" => "Intel Core i7 (12th Gen)",
|
|
"207" => "Intel Core i5 (12th Gen)",
|
|
"208" => "Intel Core i3 (12th Gen)",
|
|
_ => $"Processor Family {familyCode}"
|
|
};
|
|
}
|
|
|
|
private string MapMemoryType(string typeCode)
|
|
{
|
|
// Map memory type codes to readable names
|
|
// Based on DMTF SMBIOS Reference Specification
|
|
return typeCode switch
|
|
{
|
|
"0" => "Unknown",
|
|
"1" => "Other",
|
|
"2" => "DRAM",
|
|
"3" => "Synchronous DRAM",
|
|
"4" => "Cache DRAM",
|
|
"5" => "EDO",
|
|
"6" => "EDRAM",
|
|
"7" => "VRAM",
|
|
"8" => "SRAM",
|
|
"9" => "RAM",
|
|
"10" => "ROM",
|
|
"11" => "Flash",
|
|
"12" => "EEPROM",
|
|
"13" => "FEPROM",
|
|
"14" => "EPROM",
|
|
"15" => "CDRAM",
|
|
"16" => "3DRAM",
|
|
"17" => "SDRAM",
|
|
"18" => "SGRAM",
|
|
"19" => "RDRAM",
|
|
"20" => "DDR",
|
|
"21" => "DDR2",
|
|
"22" => "DDR2 FB-DIMM",
|
|
"24" => "DDR3",
|
|
"25" => "FBD2",
|
|
"26" => "DDR4",
|
|
"27" => "LPDDR",
|
|
"28" => "LPDDR2",
|
|
"29" => "LPDDR3",
|
|
"30" => "LPDDR4",
|
|
"31" => "Logical non-volatile device",
|
|
"32" => "HBM (High Bandwidth Memory)",
|
|
"33" => "HBM2 (High Bandwidth Memory Generation 2)",
|
|
"34" => "DDR5",
|
|
"35" => "LPDDR5",
|
|
_ => $"Type {typeCode}"
|
|
};
|
|
}
|
|
|
|
private string MapFormFactor(string formFactorCode)
|
|
{
|
|
// Map form factor codes to readable names
|
|
// Based on DMTF SMBIOS Reference Specification
|
|
return formFactorCode switch
|
|
{
|
|
"0" => "Unknown",
|
|
"1" => "Other",
|
|
"2" => "Unknown",
|
|
"3" => "SIMM",
|
|
"4" => "SIP",
|
|
"5" => "Chip",
|
|
"6" => "DIP",
|
|
"7" => "ZIP",
|
|
"8" => "Proprietary Card",
|
|
"9" => "DIMM",
|
|
"10" => "TSOP",
|
|
"11" => "Row of chips",
|
|
"12" => "RIMM",
|
|
"13" => "SODIMM",
|
|
"14" => "SRIMM",
|
|
"15" => "FB-DIMM",
|
|
_ => $"FormFactor {formFactorCode}"
|
|
};
|
|
}
|
|
}
|